0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CodeQL・Trivy・SBOM変換スクリプトでセキュリティ成果物を生成してみた

0
Last updated at Posted at 2026-09-24

はじめに

第2回では、Package Risk・SAST Exposure・CVE Dashboard が同じ Application に集約されることを確認した。

では、その情報はどこから来ているのだろうか。

Concert Protect が魔法のように生成しているわけではない。SBOM・SARIF・脆弱性スキャン結果という3種類の成果物があり、それぞれ別のジョブで生成されている。

今回は、その生成過程を追いかけてみる。

あわせて、第2回で積み残した問いにも戻る。「なぜ標準出力の SBOM はそのまま使えなかったのか」——その答えはコードの中にあった。


CI パイプラインの全体構造

.github/workflows/ci.yml は main ブランチへの push と PR 作成をトリガーに動作する。

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

ジョブの依存関係は次の通りだ。

GitHub Actions ジョブ依存グラフ
図:ci.yml のジョブ実行グラフ。第2回で取り込んだ Package SBOM・SARIF・Image Scan は、この3系統のジョブから生成されている。Concert Ingestion に収束し、CI Gate で全ジョブの成否を判定する。

セキュリティ成果物を生成する3ジョブ(sbom・codeql・trivy-image)が完了した後に concert-ingest が起動し、Concert Protect へ自動投入する。左下の Lint・テスト・Trivy Filesystem Scan は Concert 投入には関与しない。


SBOM 生成ジョブ

標準 SBOM の生成

sbom ジョブは cyclonedx-bom を使って Python 実行環境の依存関係一覧を生成する。

- name: Generate Standard SBOM
  run: cyclonedx-py environment --of json -o sbom.json

出力された sbom.json は CycloneDX 1.6 形式だ。しかしこのファイルは第2回で確認したように、Concert Protect にそのまま投入するとフォーマット不一致エラーになる。

Concert 向け変換

sbom.json を Concert 向けに変換するのが次のステップだ。

- name: Generate Concert Package SBOM
  run: python scripts/convert_sbom_for_concert.py -i sbom.json -o concert-package-sbom.json

scripts/convert_sbom_for_concert.py が行う変換は3点に絞られる。

  • metadata.component.type を application に設定する
  • metadata.component に VCS 参照(リポジトリ URL)を追加する
  • 依存コンポーネントの type を framework に変換する

この変換は、Python パッケージの一覧だった SBOM に「Application」というルート要素を与える作業だ。Concert Protect はアプリケーション単位でリスクを管理するため、「誰の依存パッケージなのか」を SBOM 内に表現する必要がある。

両ファイルは同じ Artifact としてアップロードされ、90日間保存される。

- name: Upload SBOM artifact
  uses: actions/upload-artifact@v4
  with:
    name: sbom
    path: |
      sbom.json
      concert-package-sbom.json
    retention-days: 90

CodeQL SAST ジョブ

codeql ジョブは GitHub が提供する CodeQL Action を使い、Python コードを静的解析する。

- name: Initialize CodeQL
  uses: github/codeql-action/init@v3
  with:
    languages: python
    queries: security-and-quality

- name: Perform CodeQL Analysis
  uses: github/codeql-action/analyze@v3
  with:
    output: codeql-results
    upload: false

queries: security-and-quality は CodeQL のセキュリティルールと品質ルールを両方適用するオプションだ。upload: false にしているのは、SARIF を GitHub の Security タブへ直接アップロードせず、Concert Protect に送るためだ。

出力された codeql-results/python.sarif は Artifact として保存される。

第2回で確認したように、この SARIF ファイルは変換不要で Concert Protect に取り込める。取り込み後は py/missing-call-to-init や py/import-and-import-from などのルールごとに Exposure が表示された。


Trivy スキャンジョブ

Trivy のジョブは2つある。両方とも aquasecurity/trivy-action@v0.36.0(Trivy 0.70.0)を使うが、スキャン種別が異なり、Concert への投入可否も分かれた。

trivy-fs:ファイルシステムスキャン

- name: Run Trivy filesystem scan
  uses: aquasecurity/trivy-action@v0.36.0
  with:
    scan-type: fs
    scan-ref: .
    format: json
    output: trivy-fs.json

リポジトリ内のソースコードをスキャンする。生成される trivy-fs.json の ArtifactType は repository になり、Results フィールドが欠落している場合がある。

第2回で確認したように、このファイルは Concert Protect に投入できなかった。concert-ingest ジョブのアップロード対象から除外している。Artifact としては 90日間保存されるが、Concert への投入は行わない。

trivy-image:コンテナイメージスキャン

- name: Run Trivy image scan
  uses: aquasecurity/trivy-action@v0.36.0
  with:
    scan-type: image
    image-ref: inference-sustainability-service:ci
    format: json
    output: trivy-image.json

docker-build ジョブでビルドしたコンテナイメージをスキャンする。生成される trivy-image.json の ArtifactType は container_image となり、OS パッケージ・Python パッケージそれぞれの脆弱性情報が Results に含まれる。

このファイルは Concert Protect の image_scan として正常に取り込まれ、CVE Dashboard が生成された。

同じツール、異なる結果

2つの Trivy ジョブは Action のバージョンも Trivy のバージョンも同じだ。違いはスキャン対象だけだ。

ジョブ scan-type ArtifactType Concert 投入
trivy-fs fs repository ❌ 除外
trivy-image image container_image ✅ image_scan

今回の検証では ArtifactType: container_image の出力は取り込みに成功した一方、ArtifactType: repository の出力は取り込みに失敗した。IBM Docs では Trivy のスキャン形式がサポート対象として記載されているが、今回利用した Filesystem Scan の JSON 出力が対象となるのかまでは確認できていない。


concert-ingest ジョブ:成果物を Concert へ渡す

3ジョブが完了した後、concert-ingest ジョブが各 Artifact をダウンロードして Concert Protect へ投入する。

concert-ingest:
  needs:
    - sbom
    - codeql
    - trivy-image

最初に Secrets の設定を確認し、未設定の場合(フォーク PR など)はジョブ全体をスキップする。

- name: Check secrets availability
  run: |
    if [ -z "${CONCERT_BASE_URL:-}" ] || [ -z "${CONCERT_API_KEY:-}" ]; then
      echo "configured=false" >> "$GITHUB_OUTPUT"
    else
      echo "configured=true" >> "$GITHUB_OUTPUT"
    fi

Secrets が設定されている場合は、3成果物を順番に投入する。

- name: Upload package_sbom to Concert
  run: |
    python scripts/concert_upload.py \
      --type package_sbom \
      --file artifacts/sbom/concert-package-sbom.json

- name: Upload static_code_scan to Concert
  run: |
    python scripts/concert_upload.py \
      --type static_code_scan \
      --file artifacts/codeql/python.sarif

- name: Upload image_scan to Concert
  run: |
    python scripts/concert_upload.py \
      --type image_scan \
      --file artifacts/trivy-image/trivy-image.json

投入順序は package_sbom → static_code_scan → image_scan だ。Concert Protect のデータモデルは Application 登録を前提とするため、SBOM を先に送る設計にしている。


Trivy FS スキャンを除外した理由

trivy-fs ジョブはパイプラインに残っており、成果物も生成される。しかし concert-ingest のアップロード対象には含まれていない。

第2回の手動投入検証で trivy-fs.json の Concert 投入が失敗し、構造差異を確認した。

  • ArtifactType が repository(trivy-image.json は container_image)
  • 今回の検証環境では Results フィールドが欠落していた

これを受けて、concert-ingest ジョブのアップロード対象から trivy-fs.json を除外した。IBM Docs では Trivy のスキャン形式がサポート対象として記載されているが、Filesystem Scan の出力が対象となるのかまでは確認できていない。現時点ではファイルとして保存するだけにとどめている。


検証結果のまとめ

ジョブ 生成物 Concert 投入 投入経路
sbom concert-package-sbom.json ✅ package_sbom
codeql python.sarif ✅ static_code_scan
trivy-image trivy-image.json ✅ image_scan
trivy-fs trivy-fs.json ❌ 除外

3ジョブの成果物はそれぞれ異なる経路で Concert Protect に取り込まれ、Package Inventory・SAST Exposure・CVE Dashboard を構成する。trivy-fs.json はファイルとして保存されるが、Concert への投入は行わない。

第2回では Concert Protect の画面から結果を見ていた。しかし今回パイプラインを追いかけてみると、Package Risk・SAST Exposure・CVE Dashboard は Concert が独自に生成しているわけではなく、SBOM・SARIF・Trivy の成果物をもとに構成されていることが分かった。

リスク管理の出発点は Concert Protect ではない。品質チェックやセキュリティスキャンを CI の中で継続的に実行し、その結果を蓄積することだった。


同じ構成を再現するために

同じパイプラインを構築する場合に確認が必要な点を整理する。

SBOM は検証が必要:
今回の検証環境では、cyclonedx-py の標準出力は取り込めず、変換後の SBOM のみ取り込みに成功した。どの差異が原因かまでは特定できていないため、変換スクリプトを利用している。

CodeQL は upload: false にする: デフォルトでは SARIF が GitHub Security タブへ自動アップロードされる。Concert へ送りたい場合は upload: false で出力先を Artifact に切り替える。

Trivy はスキャン種別で出力構造が変わる: scan-type: image と scan-type: fs は同じ Action でも出力 JSON の構造が異なる。今回の検証環境では、Concert に投入できたのは ArtifactType: container_image の出力だった。

フォーク PR では Concert 投入をスキップ: Secrets にアクセスできないフォーク PR では concert-ingest ジョブ全体をスキップする設計が必要だ。ci-gate はスキップを成功として扱う。


次回予告

今回は成果物の生成ロジックを整理した。

では、その Concert Protect への投入はどのように自動化されているのか。

第4回では Concert Protect の REST API 仕様を調べ、concert_upload.py がどのように実装されているかを追いかける。API の構造、HTTP 202 の意味、.sarif 拡張子問題など、実際に疎通確認した過程を記録する。


連載一覧

回 タイトル
第1回 生成AIで開発は速くなった。では、脆弱性管理は誰がやるのか?
第2回 IBM Concert Protect でソフトウェア部品とリスクを可視化してみた
第3回(本記事) CodeQL・Trivy・SBOM変換スクリプトでセキュリティ成果物を生成してみた
第4回 REST API で IBM Concert への登録を自動化してみた
第5回 コード変更からリスク可視化までを自動化してみた

検証環境

  • GitHub Actions: ubuntu-latest
  • Python: 3.12
  • cyclonedx-bom(cyclonedx-py 7.4.0 / cyclonedx-python-lib 11.12.0)
  • CodeQL: github/codeql-action/analyze@v3(queries: security-and-quality)
  • Trivy: 0.70.0(aquasecurity/trivy-action@v0.36.0)
  • IBM Concert: 3.0.x
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?