JenkinsでSBOM生成と脆弱性検知を自動化する : 2.SBOM生成と脆弱性検知パイプラインの作成

今回は、Jenkinsのパイプラインを作る機能の一つである`フリースタイルジョブ`を使って、ビルドされた成果物に対して、SBOM生成、脆弱性検知を行うビルドパイプラインを作成していきます。

Jenkinsのジョブの初期設定については、前回の記事をご覧ください。
本記事では、パイプラインの動作を記述したshellスクリプトについて解説を行います。

このパイプラインでは、以下のような前提を想定しています。
1. `main`ブランチ一つだけ利用し、開発を進める
2. ブランチへのタグ付けは行わない
3. ビルド成果物はコンテナイメージとする

本パイプラインで実行されるシェルスクリプトは、以下2つに分かれています。
1. **Script1: SBOMを生成する**
2. **Script2: 脆弱性検知を行い、その結果をSlackへ通知する**

これらのスクリプトをJenkinsジョブの設定にある`Build Steps`へshellスクリプトを書き込みます。

- [パイプラインアーキテクチャ (作成中)](#パイプラインアーキテクチャ-作成中)
- [全スクリプト (作成途中)](#全スクリプト-作成途中)
  - [SBOM用スクリプト](#sbom用スクリプト)
    - [脆弱性検知](#脆弱性検知)
- [Script1: SBOM生成](#script1-sbom生成)
  - [Step1-1 : gitのブランチ、タグ情報の抽出と環境変数の設定](#step1-1--gitのブランチタグ情報の抽出と環境変数の設定)
  - [Step1-2: ディレクトリ作成](#step1-2-ディレクトリ作成)
  - [Step1-3: コンテナイメージのビルド](#step1-3-コンテナイメージのビルド)
  - [Step1-4: SBOM生成](#step1-4-sbom生成)
- [Script4 : 脆弱性検知、結果通知用スクリプト](#script4--脆弱性検知結果通知用スクリプト)
  - [Step1: 環境変数設定](#step1-環境変数設定)
  - [生成したファイルに日付情報を付与し、保管する](#生成したファイルに日付情報を付与し保管する)
  - [脆弱性検知結果を管理するためのディレクトリ作成](#脆弱性検知結果を管理するためのディレクトリ作成)
  - [Step: 脆弱性検知用関数の作成](#step-脆弱性検知用関数の作成)
  - [Step: 脆弱性検知結果をトリアージする](#step-脆弱性検知結果をトリアージする)
  - [Teams通知メッセージの作成](#teams通知メッセージの作成)
- [Slackのwebhookの設定](#slackのwebhookの設定)
- [Slackへの通知結果](#slackへの通知結果)
- [Teamsへ通知を行う場合](#teamsへ通知を行う場合)

# パイプラインアーキテクチャ (作成中)


# 全スクリプト (作成途中)

こちら、全スクリプトです。
## SBOM用スクリプト
```bash
#現在のタグとその前のタグの環境変数を定義
#VERSION_TAG_LIST=`git tag -l 'v*'` || true
#TAG_HASH=`git rev-list ${VERSION_TAG_LIST} --max-count=1` || true
#TAG_NAME=`git describe --abbrev=0 --tags ${TAG_HASH}` || true
#PRE_TAG_HASH=`git rev-list ${VERSION_TAG_LIST} --skip=1 --max-count=1` || true
#PRE_TAG_NAME=`git describe --abbrev=0 --tags ${PRE_TAG_HASH}` || true
#echo $TAG_NAME || true
#echo $PRE_TAG_NAME || true

BRANCH_NAME=`echo $GIT_BRANCH | cut -d '/' -f 2`
REPOSITORY_NAME='Jenkins_pipeline_test'


#sbomディレクトリを作成し、最新ブランチに対してsbom_${BRANCH_NAME}.jsonを生成
SBOM_DIR=sbom/
SBOM_DIR_CURRE=sbom_curre/
SBOM_DIR_PAST=sbom_past/
SBOM_FILE="sbom_${REPOSITORY_NAME}_${BRANCH_NAME}"
SBOM_LIST_DIR=sbom_list/
SBOM_LIST_DIR_CURRE=sbom_list_curre/
SBOM_LIST_DIR_PAST=sbom_list_past/
SBOM_FILE_ARRANGED="sbom_list_${REPOSITORY_NAME}_${BRANCH_NAME}"
#ディレクトリを作る
if [ ! -d ${SBOM_DIR} ]; then
  mkdir -p ${SBOM_DIR}${SBOM_DIR_CURRE}
  mkdir -p ${SBOM_DIR}${SBOM_DIR_PAST}
fi
if [ ! -d ${SBOM_LIST_DIR} ]; then
  mkdir -p ${SBOM_LIST_DIR}${SBOM_LIST_DIR_CURRE}
  mkdir -p ${SBOM_LIST_DIR}${SBOM_LIST_DIR_PAST}
fi
if [ ! -d ${SBOM_LIST_DIR}${SBOM_LIST_DIR_CURRE}${SBOM_FILE_ARRANGED} ]; then
  mkdir -p ${SBOM_LIST_DIR}${SBOM_LIST_DIR_CURRE}${SBOM_FILE_ARRANGED}
fi
#vulnディレクトリを作成し、現在のタグに対してvuln_table_${BRANCH_NAME}.txtを生成
VULN_DIR="vuln/"
GRYPE_FILE="vuln_table_${REPOSITORY_NAME}_${BRANCH_NAME}"
VULN_DIR_CURRE="vuln_curre/"
VULN_DIR_PAST="vuln_past/"
if [ ! -d ${VULN_DIR} ]; then
  mkdir -p ${VULN_DIR}${VULN_DIR_CURRE}
  mkdir -p ${VULN_DIR}${VULN_DIR_PAST}
fi

#Docker build
imageName=poetry
imageTag="sbom"_$BUILD_ID
TARGET=$imageName:$imageTag

docker build -t ${TARGET} .


#syftでSBOMを生成する
sbom_create() {
  local _target=$1
  local _sbom_curre_file=$2
  local _sbom_past_file=$3
  syft --version
  if [ -e  "${_sbom_curre_file}" ]; then
    cp "${_sbom_curre_file}"  "${_sbom_past_file}"
  fi
  syft image:"${_target}" -o cyclonedx-json="${_sbom_curre_file}"
}
echo "---> start syft scan"
sbom_create ${TARGET} ${SBOM_DIR}${SBOM_DIR_CURRE}${SBOM_FILE}.json ${SBOM_DIR}${SBOM_DIR_PAST}${SBOM_FILE}_prev.json

docker image rm ${TARGET}
```



### 脆弱性検知
```bash
#脆弱性検知
DAY=`date +%Y%m%d%H%M`

BRANCH_NAME=`echo $GIT_BRANCH | cut -d '/' -f 2`
REPOSITORY_NAME='Jenkins_pipeline_test'

#sbomディレクトリを作成し、現在のタグに対してsbom_${TAG_NAME}.jsonを生成
#sbomディレクトリ
SBOM_DIR=sbom/
SBOM_DIR_CURRE=sbom_curre/
SBOM_DIR_PAST=sbom_past/
SBOM_FILE="sbom_${REPOSITORY_NAME}_${BRANCH_NAME}"
SBOM_DIFF_DIR=sbom_diff/


#日付処理
#cp ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt ${VULN_DIR}${VULN_DIR_PAST}${GRYPE_FILE}_${DAY}.txt
cp ${SBOM_DIR}${SBOM_DIR_CURRE}${SBOM_FILE}.json ${SBOM_DIR}${SBOM_DIR_PAST}${SBOM_FILE}_${DAY}.json


#grype sbom:./${SBOM_DIR}${SBOM_DIR_CURRE}${SBOM_FILE}.json --only-fixed -o table=${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}_fixed.txt
#grype sbom:./${SBOM_DIR}${SBOM_DIR_CURRE}${SBOM_FILE}.json --only-notfixed -o table=${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}_notfixed.txt

VULN_DIR="vuln/"
GRYPE_FILE="vuln_table_${REPOSITORY_NAME}_${BRANCH_NAME}"
VULN_DIR_CURRE="vuln_curre/"
VULN_DIR_PAST="vuln_past/"
if [ ! -d ${VULN_DIR} ]; then
  mkdir -p ${VULN_DIR}${VULN_DIR_CURRE}
  mkdir -p ${VULN_DIR}${VULN_DIR_PAST}
fi


#grypeで最新developブランチのSBOMをスキャンする
grype_vuln_scan() {
  local _sbom_target=$1
  local _results_file=$2
  #grype db status
  #export GRYPE_DB_AUTO_UPDATE=false
  #export GRYPE_CHECK_FOR_APP_UPDATE=false
  grype sbom:./"${_sbom_target}" -o table="${_results_file}".txt
  grype sbom:./"${_sbom_target}" -o json="${_results_file}".json
}

detect_high_critical_vulnerabilities_fixed() {

    local _fixed_state="$1"

    cd "${_fixed_state}"
    grep -E 'AV:N|AV:A' vuln_"${_fixed_state}"_tmp.csv | grep -E 'High|Critical' > vuln_"${_fixed_state}".csv || true
    # 直前のコマンドの終了コードを取得
    exit_code=$?
    echo ${exit_code}
    # 終了コードが0以外（エラーが発生した）場合はスキップする
    if [ $exit_code -ne 0 ]; then
        echo "${_fixed_state}" ": High or Critical are not found. Skipping the execution of the shell script."
        #exit $exit_code
    else
        #cp vulntmp.csv vulntmp1.csv
        rm vuln_"${_fixed_state}"_tmp.csv
        awk -F '","' '!seen[$1 $3]++' vuln_"${_fixed_state}".csv > vuln_"${_fixed_state}"_tmp.csv
        #sed -i '1s/^/Package name,version, purl, fixed-in, VULNERABILITY, SEVERITY, CVSS1, CVSS2, CVSS3, CVSS4\n/' vulntmp.csv
        
        awk -F '","' '{printf("| %-15s | %-12s | %-13s | %-12s | %s |\n", $1, $2, $3, $4, $5)}' vuln_"${_fixed_state}"_tmp.csv > marktable_"${_fixed_state}".txt
        sed -i '1s/^/|パッケージ名|バージョン|修正バージョン|VULNERABILITY|SEVERITY|\n/' marktable_"${_fixed_state}".txt
        sed -i '2 i\|-----------------|--------------|--------------|----------------------|----------|' marktable_"${_fixed_state}".txt
        sed 's/^\|"//g; s/"$//g' marktable_"${_fixed_state}".txt > marktable_"${_fixed_state}"0.txt
        VULNERABILITY_DATA=\`\`\`$(cat marktable_"${_fixed_state}"0.txt)\`\`\`
        rm marktable_"${_fixed_state}".txt marktable_"${_fixed_state}"0.txt
    fi
    cd ..
}

detect_high_critical_vulnerabilities_other() {

    local _fixed_state="$1"

    cd "${_fixed_state}"
    grep -E 'AV:N|AV:A' vuln_"${_fixed_state}"_tmp.csv | grep -E 'High|Critical' > vuln_"${_fixed_state}".csv || true
    # 直前のコマンドの終了コードを取得
    exit_code=$?
    echo ${exit_code}
    # 終了コードが0以外（エラーが発生した）場合はスキップする
    if [ $exit_code -ne 0 ]; then
        echo "${_fixed_state}" ": High or Critical are not found. Skipping the execution of the shell script."
        #exit $exit_code
    else
        #cp vulntmp.csv vulntmp1.csv
        rm vuln_"${_fixed_state}"_tmp.csv
        awk -F '","' '!seen[$1 $3]++' vuln_"${_fixed_state}".csv > vuln_"${_fixed_state}"_tmp.csv
        #sed -i '1s/^/Package name,version, purl, fixed-in, VULNERABILITY, SEVERITY, CVSS1, CVSS2, CVSS3, CVSS4\n/' vulntmp.csv
        
        awk -F '","' '{printf("| %-15s | %-12s | %-13s | %-12s |\n", $1, $2, $3, $4)}' vuln_"${_fixed_state}"_tmp.csv > marktable_"${_fixed_state}".txt
        sed -i '1s/^/|パッケージ名|バージョン|VULNERABILITY|SEVERITY|\n/' marktable_"${_fixed_state}".txt
        sed -i '2 i\|-----------------|--------------|--------------|----------|' marktable_"${_fixed_state}".txt
        sed 's/^\|"//g; s/"$//g' marktable_"${_fixed_state}".txt > marktable_"${_fixed_state}"0.txt
        VULNERABILITY_DATA=\`\`\`$(cat marktable_"${_fixed_state}"0.txt)\`\`\`
        rm marktable_"${_fixed_state}".txt marktable_"${_fixed_state}"0.txt
    fi
    cd ..
}

echo "---> grype vulnerability-scan"
grype_vuln_scan ${SBOM_DIR}${SBOM_DIR_CURRE}${SBOM_FILE}.json ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}

CRITICAL_NUM=`grep -o "Critical" ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt | wc -l`
HIGH_NUM=`grep -o "High" ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt | wc -l`

DIRECTORY_NAME=${GRYPE_FILE}/

if [ ! -d "${DIRECTORY_NAME}" ]; then
    mkdir -p ${DIRECTORY_NAME}
fi

if [ -e "${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.json" ]; then
	cp ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt ${DIRECTORY_NAME}
    cp ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.json ${DIRECTORY_NAME}
    cp ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt ${VULN_DIR}${VULN_DIR_PAST}${GRYPE_FILE}_${DAY}.txt
    cp ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.json ${VULN_DIR}${VULN_DIR_PAST}${GRYPE_FILE}_${DAY}.json
fi

cd $DIRECTORY_NAME

# make fixed directory to store fixed vulnerabilities.
if [ ! -d "fixed" ]; then
    mkdir -p fixed
fi
# FIXED
if cat ${GRYPE_FILE}.json | jq -r '.matches[] | .vulnerability.fix.state | contains("fixed")'  | grep true >/dev/null; then
    cat ${GRYPE_FILE}.json | jq -r '
    .matches[] 
    | select(.vulnerability.fix.state == "fixed") 
    | [
        .artifact.name,
        .artifact.version,
        .vulnerability.fix.versions[],
        if (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss = null) and (.relatedVulnerabilities[0].id as $CVE | .vulnerability.id == $CVE)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        elif (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss != null)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.vulnerability.cvss | last |.version),
            (.vulnerability.cvss | last |.vector)
        else
            .relatedVulnerabilities[0].id,
            .relatedVulnerabilities[0].severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        end
    ] | @csv' > fixed/vuln_fixed_tmp.csv

    detect_high_critical_vulnerabilities_fixed fixed
    VULNERABILITY_DATA_FIXED=${VULNERABILITY_DATA}
else
    # .vulnerability.fix.stateが"fixed"を含まない場合、メッセージを表示
    echo "The 'fixed' string is not found. Skipping the execution of the shell script."
fi

#NON-FIXED
if [ ! -d "not-fixed" ]; then
    mkdir -p not-fixed
fi

if cat ${GRYPE_FILE}.json | jq -r '.matches[] | .vulnerability.fix.state | contains("not-fixed")' | grep true >/dev/null; then
    cat ${GRYPE_FILE}.json | jq -r '
    .matches[] 
    | select(.vulnerability.fix.state == "not-fixed") 
    | [
        .artifact.name,
        .artifact.version,
        if (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss = null) and (.relatedVulnerabilities[0].id as $CVE | .vulnerability.id == $CVE)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        elif (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss != null)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.vulnerability.cvss | last |.version),
            (.vulnerability.cvss | last |.vector)
        else
            .relatedVulnerabilities[0].id,
            .relatedVulnerabilities[0].severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        end
    ] | @csv' > not-fixed/vuln_not-fixed_tmp.csv
    
    detect_high_critical_vulnerabilities_other not-fixed
    VULNERABILITY_DATA_NOTFIXED=${VULNERABILITY_DATA}

else
    # .vulnerability.fix.stateが"not-fixed"を含まない場合、メッセージを表示
    echo "The 'not-fixed' string is not found. Skipping the execution of the shell script."
fi

# UNKNOWN
if [ ! -d "unknown" ]; then
    mkdir -p unknown
fi
if cat ${GRYPE_FILE}.json | jq -r '.matches[] | .vulnerability.fix.state | contains("unknown") // contains("wont-fix")' | grep true  >/dev/null; then
    cat ${GRYPE_FILE}.json | jq -r '
    .matches[] 
    | select(.vulnerability.fix.state == "unknown" or .vulnerability.fix.state == "wont-fix") 
    | [
        .artifact.name,
        .artifact.version,
        if (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss = null) and (.relatedVulnerabilities[0].id as $CVE | .vulnerability.id == $CVE)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        elif (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss != null)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.vulnerability.cvss | last |.version),
            (.vulnerability.cvss | last |.vector)
        else
            .relatedVulnerabilities[0].id,
            .relatedVulnerabilities[0].severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        end
    ] | @csv' > unknown/vuln_unknown_tmp.csv
        
    detect_high_critical_vulnerabilities_other unknown
    VULNERABILITY_DATA_UNKOWN=${VULNERABILITY_DATA}
else
    # .vulnerability.fix.stateが"unknown"を含まない場合、メッセージを表示
    echo "The 'unknown' string is not found. Skipping the execution of the shell script."
fi

cd ..

LIB=`awk 'BEGIN {
  # テーブルヘッダーを出力
  print "| NAME | INSTALLED-VERSION |"
  print "|------|-----------|"
}
NR>1 {
  # NAME と INSTALLED 列だけを出力
  printf("| %s | %s |\n", $1, $2)
}' ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt | uniq`

JSON_INFO=`cat << EOF
{
    "text": "レベルHigh, Criticalの脆弱性が見つかりました。",
    "blocks": [
    	{
    		"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "レベルHigh, Criticalの脆弱性が見つかりました。:"
    		}
    	},
    	{
    		"type": "section",
    		"block_id": "section789",
    		"fields": [
    			{
    				"type": "mrkdwn",
    				"text": "*日付*: ${DAY}"
    			},
                {
    				"type": "mrkdwn",
    				"text": "*branch*: ${BRANCH_NAME}"
    			},
                {
    				"type": "mrkdwn",
    				"text": "*JOB name*: ${JOB_NAME} #${BUILD_ID}"
    			},
                {
    				"type": "mrkdwn",
    				"text": "*JOBのURL*: ${BUILD_URL}"
    			},
                {
    				"type": "mrkdwn",
    				"text": "*CRITICALの件数*: ${CRITICAL_NUM}"
    			},
                {
    				"type": "mrkdwn",
    				"text": "*HIGHの件数*: ${HIGH_NUM}"
    			},
    		]
    	},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "FIXED-INに記されたバージョンへアップデートをお願いします"
    		}
		},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "${VULNERABILITY_DATA_FIXED}"
    		}
		},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "次の脆弱性については、修正バージョンが公開されていません。"
    		}
		},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "${VULNERABILITY_DATA_NOTFIXED}"
    		}
		},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "次の脆弱性については、脆弱性対策が知られていません。"
    		},
		},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "${VULNERABILITY_DATA_UNKOWN}"
    		}
    	}
    ]
}
EOF`

#プロキシを設定する場合は、プロキシを環境変数で設定
#export HTTP_PROXY=
#export HTTPS_PROXY=

#Web hook用URLの設定
WEBHOOK_URL=https://hooks.slack.com/services/

if grep -q "Critical" ${DIRECTORY_NAME}${GRYPE_FILE}.json || grep -q "High" ${DIRECTORY_NAME}${GRYPE_FILE}.json; then
  echo "alerm"

  curl -X POST -H 'Content-type: application/json' -d "${JSON_INFO}" "${WEBHOOK_URL}"    
  #curl -H "Content-Type: application/json" -d "${JSON_INFO}" "${WEBHOOK_URL}"
fi
```


# Script1: SBOM生成
## Step1-1 : gitのブランチ、タグ情報の抽出と環境変数の設定
まず、パイプラインで生成されるディレクトリ、ファイル名にgitのリポジトリ、ブランチの情報を付与するため、これらの情報の環境変数を設定します。
このスクリプトで使用する環境変数を以下表にまとめました。

| 環境変数            | 説明 | 
| ------------------- | ---- | 
| VERSION_TAG_LIST    | バージョンづけされたタグのリスト | 
| TAG_HASH            | タグのリストから現在で最新バージョンタグのハッシュ値を取得する | 
| TAG_NAME            |最新バージョンタグの値を取得する| 
| PRE_TAG_HASH        |前回作成したバージョンタグのハッシュ値を取得する| 
| PRE_TAG_NAME        | 前回作成したバージョンタグの値を取得する| 
| BRANCH_NAME         |現在のブランチ名を取得する| 
| REPOSITORY_NAME     |リポジトリ名を設定する| 


```bash
echo "Hello, Jenkins."
echo "---> set environment variable"

BRANCH_NAME=`echo $GIT_BRANCH | cut -d '/' -f 2`
REPOSITORY_NAME='Jenkins_pipeline_test'
```



## Step1-2: ディレクトリ作成
次に、SBOMのファイル名、また、そのファイルを保存するためのディレクトリ名を環境変数として設定します。

| 環境変数            | 説明 | 
| ------------------- | ---- | 
| SBOM_DIR            |SBOMを保管するためのディレクトリ| 
| SBOM_DIR_CURRE      |最新のSBOMを保管するためのディレクトリ| 
| SBOM_DIR_PAST       |過去のSBOMを保管するためのディレクトリ| 
| SBOM_FILE           |今回のジョブで生成するSBOMのファイル名|

| 環境変数            | 説明 | 
| ------------------- | ---- | 
| SBOM_LIST_DIR       |SBOMから作成したソフトウェアリストを保管するためのディレクトリ| 
| SBOM_LIST_DIR_CURRE |最新のソフトウェアリストを保管するためのディレクトリ| 
| SBOM_LIST_DIR_PAST  |過去のソフトウェアリストを保管するためのディレクトリ| 
| SBOM_LIST_FILE  |今回作成したSBOMから作成したソフトウェアリスト| 


| 環境変数            | 説明 | 
| ------------------- | ---- | 
| SBOM_DIFF_DIR       |SBOMの差分を保管するためのディレクトリ| 
| SBOM_DIFF_FILE      |SBOMの差分結果が記述されたファイル|

設定した環境変数に従い、ディレクトリを生成します。
```bash
#sbomディレクトリを作成し、mainブランチの最新のコミットに対してsbom_${REPOSITORY_NAME}_${BRANCH_NAME}.jsonを生成
SBOM_DIR=sbom/
SBOM_DIR_CURRE=sbom_curre/
SBOM_DIR_PAST=sbom_past/
SBOM_FILE="sbom_${REPOSITORY_NAME}_${BRANCH_NAME}"

#SBOMを保管するディレクトリを作成
if [ ! -d ${SBOM_DIR} ]; then
  mkdir -p ${SBOM_DIR}${SBOM_DIR_CURRE}
  mkdir -p ${SBOM_DIR}${SBOM_DIR_PAST}
fi

#脆弱性検知結果を保管するディレクトリを作成
VULN_DIR="vuln/"
GRYPE_FILE="vuln_table_${REPOSITORY_NAME}_${BRANCH_NAME}"
VULN_DIR_CURRE="vuln_curre/"
VULN_DIR_PAST="vuln_past/"
if [ ! -d ${VULN_DIR} ]; then
  mkdir -p ${VULN_DIR}${VULN_DIR_CURRE}
  mkdir -p ${VULN_DIR}${VULN_DIR_PAST}
fi
```

## Step1-3: コンテナイメージのビルド
コンテナイメージをビルドします。ここでビルドされたコンテナイメージを`syft`でスキャンし、SBOMを生成します。
| 環境変数            | 説明 | 
| ------------------- | ---- | 
| IMAGE_NAME            |イメージ名| 
| IMAGE_TAG      |イメージのタグ名| 
| TARGET              |syftでスキャンするコンテナイメージ| 

```bash
#Docker build
IMAGE_NAME=poetry
IMAGE_TAG="sbom"_$BUILD_ID
TARGET=$IMAGE_NAME:$IMAGE_TAG

docker build -t ${TARGET} .
```

## Step1-4: SBOM生成
syftを使い、SBOMを生成します。ここでは、syftによるSBOM生成を`sbom_create()`という関数にしています。
```bash
#syftでSBOMを生成する
sbom_create() {
  local _target=$1
  local _sbom_curre_file=$2
  local _sbom_past_file=$3
  syft --version
  #新しいSBOMをスキャンする前に、前回生成したSBOMはSBOM名_prev.jsonへコピー
  if [ -e  "${_sbom_curre_file}" ]; then
    cp "${_sbom_curre_file}"  "${_sbom_past_file}"
  fi
  syft image:"${_target}" -o cyclonedx-json="${_sbom_curre_file}"
}
echo "---> start syft scan"
sbom_create ${TARGET} ${SBOM_DIR}${SBOM_DIR_CURRE}${SBOM_FILE}.json ${SBOM_DIR}${SBOM_DIR_PAST}${SBOM_FILE}_prev.json

#スキャン後、イメージは削除
docker image rm ${TARGET}
```
前回のSBOMが存在する場合、新しいSBOMをスキャンする前に、前回生成したSBOMをSBOM名_prev.jsonというファイルへコピーしています。

上のスクリプトにおいて、syftの各オプション、引数は次の意味があります。
- `image:"${_target}"`
  - コンテナイメージに対して、スキャンを実施。`:`の後にSBOM生成対象となるコンテナイメージを指定する
- `-o cyclonedx-json="${_sbom_curre_file}"`
  - SBOMファイルの出力をcyclonedx形式かつjsonフォーマットで出力する

# Script4 : 脆弱性検知、結果通知用スクリプト

## Step1: 環境変数設定

```bash
DAY=`date +%Y%m%d%H%M`

BRANCH_NAME=`echo $GIT_BRANCH | cut -d '/' -f 2`
REPOSITORY_NAME='Jenkins_pipeline_test'

#sbomディレクトリを作成し、現在のタグに対してsbom_${TAG_NAME}.jsonを生成
#sbomディレクトリ
SBOM_DIR=sbom/
SBOM_DIR_CURRE=sbom_curre/
SBOM_DIR_PAST=sbom_past/
SBOM_FILE="sbom_${REPOSITORY_NAME}_${BRANCH_NAME}"
SBOM_DIFF_DIR=sbom_diff/
SBOM_DIFF_FILE1=sbom_diff_${REPOSITORY_NAME}_New_${BRANCH_NAME}_Prev_${BRANCH_NAME}
#SBOM_DIFF_FILE2=sbom_diff_${REPOSITORY_NAME}_New_${BRANCH_NAME}_${TAG_NAME}
```

## 生成したファイルに日付情報を付与し、保管する
```bash
#日付処理
if [ -e  ${SBOM_DIFF_DIR}${SBOM_DIFF_FILE1}.txt ]; then
  cp ${SBOM_DIFF_DIR}${SBOM_DIFF_FILE1}.txt ${SBOM_DIFF_DIR}${SBOM_DIFF_FILE1}_${DAY}.txt
  echo "END"
fi

cp ${SBOM_DIR}${SBOM_DIR_CURRE}${SBOM_FILE}.json ${SBOM_DIR}${SBOM_DIR_PAST}${SBOM_FILE}_${DAY}.json
```


## 脆弱性検知結果を管理するためのディレクトリ作成
| 環境変数            | 説明 | 
| ------------------- | ---- | 
| VULN_DIR            |脆弱性検知結果を保管するためのディレクトリ| 
| GRYPE_FILE          |脆弱性検知結果のファイル名| 
| VULN_DIR_CURRE      |最新の脆弱性検知結果を保管するためのディレクトリ| 
| VULN_DIR_PAST       |過去の脆弱性検知結果を保管するためのディレクトリ| 
```bash
#脆弱性検知
#vulnディレクトリを作成し、現在のタグに対してvuln_table_${BRANCH_NAME}.txtを生成
VULN_DIR="vuln/"
GRYPE_FILE="vuln_table_${REPOSITORY_NAME}_${BRANCH_NAME}"
VULN_DIR_CURRE="vuln_curre/"
VULN_DIR_PAST="vuln_past/"
if [ ! -d ${VULN_DIR} ]; then
  mkdir -p ${VULN_DIR}${VULN_DIR_CURRE}
  mkdir -p ${VULN_DIR}${VULN_DIR_PAST}
fi
```
## Step: 脆弱性検知用関数の作成
```bash
#grypeで最新developブランチのSBOMをスキャンする
grype_vuln_scan() {
  local _sbom_target=$1
  local _results_file=$2
  #grype db status
  #export GRYPE_DB_AUTO_UPDATE=false
  #export GRYPE_CHECK_FOR_APP_UPDATE=false
  grype sbom:./"${_sbom_target}" -o table="${_results_file}".txt
  grype sbom:./"${_sbom_target}" -o json="${_results_file}".json
}
```

以下の環境変数の設定は、grypeがSBOMをスキャンする前に、データベースをアップデートされる操作をオフにする設定です。
```bash
export GRYPE_DB_AUTO_UPDATE=false
export GRYPE_CHECK_FOR_APP_UPDATE=false
```

grypeコマンドのオプションにはそれぞれ次のような意味があります
- `sbom:./${SBOM_DIR}${SBOM_DIR_CURRE}${SBOM_FILE}.json` SBOMのスキャンを行う
- `-o table=${SCAN_DIR}${SCAN_DIR_CURRE}${GRYPE_FILE}.txt`　出力形式を指定。今回はtxtファイルでテーブルを出力する
- jsonで出力したい場合は `-o json=${SCAN_DIR}${SCAN_DIR_CURRE}${GRYPE_FILE}.json`とする



## Step: 脆弱性検知結果をトリアージする
grypeで出力した脆弱性結果から、
1. 脆弱性レベルがHigh、Criticalのもの
2. アタックベクトルがネットワーク経由で攻撃可能なもの`AV:N`
を抽出します。
3. 次に、fixedされているか、いないかで場合分けを行っています。
```bash
detect_high_critical_vulnerabilities_fixed() {

    local _fixed_state="$1"

    cd "${_fixed_state}"
    grep -E 'AV:N|AV:A' vuln_"${_fixed_state}"_tmp.csv | grep -E 'High|Critical' > vuln_"${_fixed_state}".csv || true
    # 直前のコマンドの終了コードを取得
    exit_code=$?
    echo ${exit_code}
    # 終了コードが0以外（エラーが発生した）場合はスキップする
    if [ $exit_code -ne 0 ]; then
        echo "${_fixed_state}" ": High or Critical are not found. Skipping the execution of the shell script."
        #exit $exit_code
    else
        #cp vulntmp.csv vulntmp1.csv
        rm vuln_"${_fixed_state}"_tmp.csv
        awk -F '","' '!seen[$1 $3]++' vuln_"${_fixed_state}".csv > vuln_"${_fixed_state}"_tmp.csv
        #sed -i '1s/^/Package name,version, purl, fixed-in, VULNERABILITY, SEVERITY, CVSS1, CVSS2, CVSS3, CVSS4\n/' vulntmp.csv
        
        awk -F '","' '{printf("| %-15s | %-12s | %-13s | %-12s | %s |\n", $1, $2, $3, $4, $5)}' vuln_"${_fixed_state}"_tmp.csv > marktable_"${_fixed_state}".txt
        sed -i '1s/^/|パッケージ名|バージョン|修正バージョン|VULNERABILITY|SEVERITY|\n/' marktable_"${_fixed_state}".txt
        sed -i '2 i\|-----------------|--------------|--------------|----------------------|----------|' marktable_"${_fixed_state}".txt
        sed 's/^\|"//g; s/"$//g' marktable_"${_fixed_state}".txt > marktable_"${_fixed_state}"0.txt
        VULNERABILITY_DATA=\`\`\`$(cat marktable_"${_fixed_state}"0.txt)\`\`\`
        rm marktable_"${_fixed_state}".txt marktable_"${_fixed_state}"0.txt
    fi
    cd ..
}
```
```bash
detect_high_critical_vulnerabilities_other() {

    local _fixed_state="$1"

    cd "${_fixed_state}"
    grep -E 'AV:N|AV:A' vuln_"${_fixed_state}"_tmp.csv | grep -E 'High|Critical' > vuln_"${_fixed_state}".csv || true
    # 直前のコマンドの終了コードを取得
    exit_code=$?
    echo ${exit_code}
    # 終了コードが0以外（エラーが発生した）場合はスキップする
    if [ $exit_code -ne 0 ]; then
        echo "${_fixed_state}" ": High or Critical are not found. Skipping the execution of the shell script."
        #exit $exit_code
    else
        #cp vulntmp.csv vulntmp1.csv
        rm vuln_"${_fixed_state}"_tmp.csv
        awk -F '","' '!seen[$1 $3]++' vuln_"${_fixed_state}".csv > vuln_"${_fixed_state}"_tmp.csv
        #sed -i '1s/^/Package name,version, purl, fixed-in, VULNERABILITY, SEVERITY, CVSS1, CVSS2, CVSS3, CVSS4\n/' vulntmp.csv
        
        awk -F '","' '{printf("| %-15s | %-12s | %-13s | %-12s |\n", $1, $2, $3, $4)}' vuln_"${_fixed_state}"_tmp.csv > marktable_"${_fixed_state}".txt
        sed -i '1s/^/|パッケージ名|バージョン|VULNERABILITY|SEVERITY|\n/' marktable_"${_fixed_state}".txt
        sed -i '2 i\|-----------------|--------------|--------------|----------|' marktable_"${_fixed_state}".txt
        sed 's/^\|"//g; s/"$//g' marktable_"${_fixed_state}".txt > marktable_"${_fixed_state}"0.txt
        VULNERABILITY_DATA=\`\`\`$(cat marktable_"${_fixed_state}"0.txt)\`\`\`
        rm marktable_"${_fixed_state}".txt marktable_"${_fixed_state}"0.txt
    fi
    cd ..
}

```
```bash
echo "---> grype vulnerability-scan"
grype_vuln_scan ${SBOM_DIR}${SBOM_DIR_CURRE}${SBOM_FILE}.json ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}

CRITICAL_NUM=`grep -o "Critical" ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt | wc -l`
HIGH_NUM=`grep -o "High" ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt | wc -l`
```
```bash
DIRECTORY_NAME=${GRYPE_FILE}/

if [ ! -d "${DIRECTORY_NAME}" ]; then
    mkdir -p ${DIRECTORY_NAME}
fi

if [ -e "${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.json" ]; then
	cp ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt ${DIRECTORY_NAME}
    cp ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.json ${DIRECTORY_NAME}
    cp ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt ${VULN_DIR}${VULN_DIR_PAST}${GRYPE_FILE}_${DAY}.txt
    cp ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.json ${VULN_DIR}${VULN_DIR_PAST}${GRYPE_FILE}_${DAY}.json
fi

cd $DIRECTORY_NAME
```
```bash
# make fixed directory to store fixed vulnerabilities.
if [ ! -d "fixed" ]; then
    mkdir -p fixed
fi
# FIXED
if cat ${GRYPE_FILE}.json | jq -r '.matches[] | .vulnerability.fix.state | contains("fixed")'  | grep true >/dev/null; then
    cat ${GRYPE_FILE}.json | jq -r '
    .matches[] 
    | select(.vulnerability.fix.state == "fixed") 
    | [
        .artifact.name,
        .artifact.version,
        .vulnerability.fix.versions[],
        if (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss = null) and (.relatedVulnerabilities[0].id as $CVE | .vulnerability.id == $CVE)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        elif (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss != null)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.vulnerability.cvss | last |.version),
            (.vulnerability.cvss | last |.vector)
        else
            .relatedVulnerabilities[0].id,
            .relatedVulnerabilities[0].severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        end
    ] | @csv' > fixed/vuln_fixed_tmp.csv

    detect_high_critical_vulnerabilities_fixed fixed
    VULNERABILITY_DATA_FIXED=${VULNERABILITY_DATA}
else
    # .vulnerability.fix.stateが"fixed"を含まない場合、メッセージを表示
    echo "The 'fixed' string is not found. Skipping the execution of the shell script."
fi
```
```bash
#NON-FIXED
if [ ! -d "not-fixed" ]; then
    mkdir -p not-fixed
fi

if cat ${GRYPE_FILE}.json | jq -r '.matches[] | .vulnerability.fix.state | contains("not-fixed")' | grep true >/dev/null; then
    cat ${GRYPE_FILE}.json | jq -r '
    .matches[] 
    | select(.vulnerability.fix.state == "not-fixed") 
    | [
        .artifact.name,
        .artifact.version,
        if (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss = null) and (.relatedVulnerabilities[0].id as $CVE | .vulnerability.id == $CVE)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        elif (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss != null)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.vulnerability.cvss | last |.version),
            (.vulnerability.cvss | last |.vector)
        else
            .relatedVulnerabilities[0].id,
            .relatedVulnerabilities[0].severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        end
    ] | @csv' > not-fixed/vuln_not-fixed_tmp.csv
    
    detect_high_critical_vulnerabilities_other not-fixed
    VULNERABILITY_DATA_NOTFIXED=${VULNERABILITY_DATA}

else
    # .vulnerability.fix.stateが"not-fixed"を含まない場合、メッセージを表示
    echo "The 'not-fixed' string is not found. Skipping the execution of the shell script."
fi
```
```bash
# UNKNOWN
if [ ! -d "unknown" ]; then
    mkdir -p unknown
fi
if cat ${GRYPE_FILE}.json | jq -r '.matches[] | .vulnerability.fix.state | contains("unknown") // contains("wont-fix")' | grep true  >/dev/null; then
    cat ${GRYPE_FILE}.json | jq -r '
    .matches[] 
    | select(.vulnerability.fix.state == "unknown" or .vulnerability.fix.state == "wont-fix") 
    | [
        .artifact.name,
        .artifact.version,
        if (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss = null) and (.relatedVulnerabilities[0].id as $CVE | .vulnerability.id == $CVE)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        elif (.vulnerability.id | contains("CVE-")) and (.vulnerability.cvss != null)
        then
            .vulnerability.id,
            .vulnerability.severity,
            (.vulnerability.cvss | last |.version),
            (.vulnerability.cvss | last |.vector)
        else
            .relatedVulnerabilities[0].id,
            .relatedVulnerabilities[0].severity,
            (.relatedVulnerabilities[0].cvss | last | .version),
            (.relatedVulnerabilities[0].cvss | last | .vector)
        end
    ] | @csv' > unknown/vuln_unknown_tmp.csv
        
    detect_high_critical_vulnerabilities_other unknown
    VULNERABILITY_DATA_UNKOWN=${VULNERABILITY_DATA}
else
    # .vulnerability.fix.stateが"unknown"を含まない場合、メッセージを表示
    echo "The 'unknown' string is not found. Skipping the execution of the shell script."
fi

cd ..
```

## Teams通知メッセージの作成
上でトリアージし、修正されているか、いないかで場合分けした脆弱性を通知するためのメッセージを作成します。今回は、Slackへ通知していますが、Teamsへ通知することも可能です。

```bash

LIB=`awk 'BEGIN {
  # テーブルヘッダーを出力
  print "| NAME | INSTALLED-VERSION |"
  print "|------|-----------|"
}
NR>1 {
  # NAME と INSTALLED 列だけを出力
  printf("| %s | %s |\n", $1, $2)
}' ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt | uniq`

JSON_INFO=`cat << EOF
{
    "text": "レベルHigh, Criticalの脆弱性が見つかりました。",
    "blocks": [
    	{
    		"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "レベルHigh, Criticalの脆弱性が見つかりました。:"
    		}
    	},
    	{
    		"type": "section",
    		"block_id": "section789",
    		"fields": [
    			{
    				"type": "mrkdwn",
    				"text": "*日付*: ${DAY}"
    			},
                {
    				"type": "mrkdwn",
    				"text": "*branch*: ${BRANCH_NAME}"
    			},
                {
    				"type": "mrkdwn",
    				"text": "*JOB name*: ${JOB_NAME} #${BUILD_ID}"
    			},
                {
    				"type": "mrkdwn",
    				"text": "*JOBのURL*: ${BUILD_URL}"
    			},
                {
    				"type": "mrkdwn",
    				"text": "*CRITICALの件数*: ${CRITICAL_NUM}"
    			},
                {
    				"type": "mrkdwn",
    				"text": "*HIGHの件数*: ${HIGH_NUM}"
    			},
    		]
    	},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "FIXED-INに記されたバージョンへアップデートをお願いします"
    		}
		},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "${VULNERABILITY_DATA_FIXED}"
    		}
		},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "次の脆弱性については、修正バージョンが公開されていません。"
    		}
		},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "${VULNERABILITY_DATA_NOTFIXED}"
    		}
		},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "次の脆弱性については、脆弱性対策が知られていません。"
    		},
		},
        {
        	"type": "section",
    		"text": {
    			"type": "mrkdwn",
    			"text": "${VULNERABILITY_DATA_UNKOWN}"
    		}
    	}
    ]
}
EOF`

#プロキシを設定する場合は、プロキシを環境変数で設定
#export HTTP_PROXY=
#export HTTPS_PROXY=

#Web hook用URLの設定
WEBHOOK_URL=https://hooks.slack.com/services/

if grep -q "Critical" ${DIRECTORY_NAME}${GRYPE_FILE}.json || grep -q "High" ${DIRECTORY_NAME}${GRYPE_FILE}.json; then
  echo "alerm"
  
  curl -X POST -H 'Content-type: application/json' -d "${JSON_INFO}" "${WEBHOOK_URL}"
  #curl -H "Content-Type: application/json" -d "${JSON_INFO}" "${WEBHOOK_URL}"
fi
```

# Slackのwebhookの設定
Slackのグループ、チャンネルを作成してください。今回、私は`jenkinsの通知テスト`というチャンネルを作成しました。

以下のURLに行き、Webhook用のURLを作成・コピーしてください。
https://slack.com/services/new/incoming-webhook

![Jenkins_Slack1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/76f36190-c6f7-5c64-a0fb-79559361f7e6.png)
![Jenkins_Slack2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/e381bd70-c5f6-77a1-7b5d-9955d47ae26c.png)


# Slackへの通知結果
Jenkinsのジョブを実行し、`Finished: SUCCESS`と出ていれば、スクリプトの実行は成功し、Slackへ通知メッセージが送信されているはずです。
![Jenkins_Slack3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/7cede34b-92c8-7a7a-fc9f-3e60f0a08fa4.png)

通知メッセージは以下のように送信されます。1段目にJenkinsのジョブや、脆弱数などの基本情報を、その後に、検知された脆弱性の情報を載せています。
![Jenkins_Slack4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781216/672fb756-a3da-481a-2031-366291e1f4db.png)


# Teamsへ通知を行う場合
本記事では、Slackへ通知を行いましたが、脆弱性検知結果をTeamsへ通知することもできます。ここでは、Teamsへの通知方法についても解説します。
まず、以下のURL記載の方法でteamsのwebhookを作成します。
https://learn.microsoft.com/ja-jp/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=newteams%2Cdotnet

webhookのURLを変数`WEBHOOK_URL`に代入し、以下のjsonメッセージをURLへ渡せば、Teamsへ通知できます。

Teamsの方がMarkdownテーブルが使えるので、きれいに通知できます。

[Slackのwebhooksの設定](https://slack.com/services/new/incoming-webhook)

```json
{
    "schema": "https://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.0",
    "@type": "MessageCard",
    "@context": "http://schema.org/extensions",
    "themeColor": "d70048",
    "summary": "Grype脆弱性情報通知",
    "title": "Grype脆弱性情報通知",
    "sections": [
        {
            "title": "Details",
            "markdown": true,
            "text": "レベルHigh, Criticalの脆弱性が見つかりました。以下が対象のファイルです: \r - **SBOMリスト : ${SBOM_LIST_DIR}${SBOM_LIST_DIR_CURRE}${SBOM_FILE_ARRANGED}.txt** \r - **vuln結果 : ${VULN_DIR}${VULN_DIR_CURRE}${GRYPE_FILE}.txt**",
            "facts": [
            	{
                    "name": "日付",
                    "value": "${DAY}"
                },
                {
                    "name": "ブランチ",
                    "value": "main"
                },
                {
                    "name": "job名",
                    "value": "${JOB_NAME} #${BUILD_ID}"
                },
                {
                    "name": "CRITICALの件数",
                    "value": "${CRITICAL_NUM}"
                },
                {
                    "name": "HIGHの件数",
                    "value": "${HIGH_NUM}"
                },
            ],
        },
        {
            "markdown": true ,
            "text": "以下のライブラリに対処が必要な脆弱性が検知されました。ご確認ください。",
        },
        {
            "markdown": true,
            "text": "${LIB}"
        },
        {
            "markdown": true ,
            "text": "FIXED-INに記されたバージョンへアップデートをお願いします"
        },
        {
            "markdown": true,
            "text": "${VULNERABILITY_DATA_FIXED}"
        },
        {
            "markdown": true ,
            "text": "次の脆弱性については、修正バージョンが公開されていません。"
        },
        {
            "markdown": true,
            "text": "${VULNERABILITY_DATA_NOTFIXED}"
        },
        {
            "markdown": true ,
            "text": "次の脆弱性については、脆弱性対策が知られていません。"
        },
        {
        	"markdown": true,
            "text": "${VULNERABILITY_DATA_UNKOWN}"
        },
        {
            "markdown": true ,
            "text": "以下からジョブを確認できます"
        }
    ],
    "potentialAction": [
       {
          "@type": "OpenUri",
          "name": "Jenkinsでジョブ結果を確認する",
          "targets": [
            {
              "os": "default",
              "uri": "${BUILD_URL}"
            }
         ]
       }
    ]
}
```