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?

REST API で IBM Concert への登録を自動化してみた

0
Last updated at Posted at 2026-09-24

はじめに

第2回では Concert Protect に手動で成果物を投入した。第3回では、その成果物が GitHub Actions でどのように生成されるかを追いかけた。

手動投入の問題は明確だ。毎回 GitHub Actions から Artifacts をダウンロードし、Concert の UI を開いて、ファイルを選択してアップロードする。main ブランチに push されるたびにこれを繰り返すのは現実的ではない。

今回は、この手動手順を REST API 経由で自動化するまでの過程を記録する。API 仕様の調査から始まり、Python の Upload Client を実装し、実際に HTTP 202 が返ってくるまでを追いかける。


まず API の仕様を調べた

自動化のための第一歩は、Concert Protect に REST API が存在するかどうかの確認だった。

結果として、IBM Docs に API ドキュメントが公開されており、GitHub にも OpenAPI 仕様書が存在することを確認した。

POST /ingestion/api/v1/upload_files

すべての成果物投入に共通するエンドポイントはこの1つだ。data_type フィールドで投入種別を切り替える設計になっている。

成果物 data_type
concert-package-sbom.json package_sbom
python.sarif static_code_scan
trivy-image.json image_scan

第2回で「投入種別」ごとに画面が違うと感じていた理由はここにあった。Concert は入力経路を data_type で分けて管理している。

認証方式

認証は API Key 方式だ。Concert UI でキーを生成し、HTTP ヘッダーに付与する。

Authorization: C_API_KEY <API_key>
InstanceId: <instance_id>
Content-Type: multipart/form-data

API Key は生成時にしか表示されない。紛失した場合は再生成が必要になる。

metadata の設計

リクエストは multipart/form-data で構成される。data_type と filename(ファイル本体)に加え、metadata を JSON 文字列として付与する。

curl -X POST \
  "https://<concert_host>/ingestion/api/v1/upload_files" \
  -H "Authorization: C_API_KEY ${CONCERT_API_KEY}" \
  -H "InstanceId: ${CONCERT_INSTANCE_ID}" \
  -H "Content-Type: multipart/form-data" \
  -F "data_type=package_sbom" \
  -F 'metadata={"repo_url": "https://github.com/...", "repo_name": "..."}' \
  -F "filename=@concert-package-sbom.json"

IBM Docs の File Upload Matrix では package_sbom と static_code_scan で repo_url と repo_name が必須と記載されている。今回の検証でもこれらを付与した上で投入している。image_scan は metadata がすべてオプションになっている。Application との関連付けには application_name / application_version を付与する。

この時点では実機での動作は未確認だった。「仕様として存在する」ことを確認しただけだ。


Upload Client を実装した

API 仕様が確認できたため、scripts/concert_upload.py として Upload Client を実装した。

設計の方針は2点だ。

  • 外部ライブラリを使わない — Python 標準ライブラリ(urllib・json・argparse)のみで実装する。GitHub Actions 上で pip install なしで動作する
  • dry-run モードを先に作る — API 接続情報がなくても送信内容を確認できるようにする

UploadType Enum

投入種別は Enum で管理する。文字列のタイプミスを防ぐためだ。

class UploadType(enum.Enum):
    PackageSBOM    = "package_sbom"
    StaticCodeScan = "static_code_scan"
    ImageScan      = "image_scan"

metadata 生成

投入種別に応じて必要な metadata を組み立てる。

def build_metadata(upload_type: UploadType, config: ConcertConfig) -> dict:
    base = {
        "application_name": config.application_name,
        "application_version": config.application_version,
    }
    if upload_type is UploadType.PackageSBOM:
        return {"repo_url": config.repo_url, "repo_name": config.repo_name, **base}
    if upload_type is UploadType.StaticCodeScan:
        return {"repo_url": config.repo_url, "repo_name": config.repo_name,
                "scanner_name": "CodeQL", **base}
    return {"scanner_name": "Trivy", **base}

multipart/form-data を stdlib だけで構築

requests ライブラリを使えば簡単だが、外部依存を避けるために stdlib の urllib で multipart ボディを組み立てた。

def _build_multipart(fields, file_field, file_bytes, upload_name, boundary):
    crlf = b"\r\n"
    parts = []
    for name, value in fields.items():
        parts.append(
            f"--{boundary}".encode() + crlf
            + f'Content-Disposition: form-data; name="{name}"'.encode() + crlf
            + crlf + value.encode() + crlf
        )
    # ファイルパート
    parts.append(
        f"--{boundary}".encode() + crlf
        + f'Content-Disposition: form-data; name="{file_field}"; filename="{upload_name}"'.encode() + crlf
        + b"Content-Type: application/octet-stream" + crlf
        + crlf + file_bytes + crlf
    )
    parts.append(f"--{boundary}--".encode() + crlf)
    return b"".join(parts)

CLI インターフェース

python scripts/concert_upload.py --type package_sbom    --file concert-package-sbom.json
python scripts/concert_upload.py --type static_code_scan --file python.sarif
python scripts/concert_upload.py --type image_scan      --file trivy-image.json
python scripts/concert_upload.py --type package_sbom    --file concert-package-sbom.json --dry-run

実機疎通で出てきた問題

実装が完了した後、実際の Concert インスタンスで疎通確認を行った。

結果は3成果物すべてで HTTP 202 が返ってきた。しかし、その前にいくつかの問題に当たった。

問題1:.sarif 拡張子を Concert が拒否する

python.sarif をそのままアップロードしたところ、HTTP 400 が返ってきた。

❌ Upload failed (HTTP 400) | type=static_code_scan | file=python.sarif

SARIF は JSON ベースの形式であり、ファイルの中身自体は問題なかった。しかし今回の検証環境では、.sarif のままでは HTTP 400 となり、.json に変更すると受け付けられた。

対処として、アップロード時に拡張子を自動変換する処理を追加した。ファイル本体は変更しない。送信時のファイル名だけを .json に変換する。

def _resolve_upload_filename(file_path: Path) -> tuple[bytes, str]:
    file_bytes = file_path.read_bytes()
    upload_name = file_path.name
    if upload_name.endswith(".sarif"):
        upload_name = upload_name[:-len(".sarif")] + ".json"
        logger.info("Renaming %s → %s for Concert upload", file_path.name, upload_name)
    return file_bytes, upload_name

問題2:検証環境が自己署名証明書を使っていた

検証環境の Concert インスタンスは自己署名証明書を使っていたため、TLS 検証で失敗した。

--insecure フラグを追加して対処した。本番環境では使用しない。

if insecure:
    ssl_ctx = ssl.create_default_context()
    ssl_ctx.check_hostname = False
    ssl_ctx.verify_mode = ssl.CERT_NONE

実機疎通確認結果

3成果物すべてで HTTP 202 が返ってくることを確認した。

成果物 data_type HTTP 結果
concert-package-sbom.json package_sbom 202 ✅
python.sarif static_code_scan 202 ✅
trivy-image.json image_scan 202 ✅

各投入のレスポンスには event_ids が含まれており、Concert の Event Log で追跡できる。

✅ Upload accepted (HTTP 202) | type=package_sbom | file=concert-package-sbom.json
Response: {"record_paths":["<instance_id>/roja/package_sbom/2026/9/20/<id>_concert-package-sbom.json"],
           "event_ids":["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
           "job_data":{"message":"202 Accepted"}}

Concert 側の Event Log を確認すると、python.json・trivy-image.json・concert-package-sbom.json が daihiraoka 1.0.0 に紐付いて「処理完了」として記録されていた。

Concert Event Log
図:Concert Protect の Event Log 画面。パッケージ SBOM・静的コード・スキャン・イメージ・スキャンの3種類が「処理完了」として記録されている。python.json は .sarif から自動変換された結果だ。

なお、ICSC_SAST_daihiraoka_trivy_packages_... というファイル名の「コード・スキャン」が「エラー処理」として記録されている。ファイル名のプレフィックスから、IBM Concert Secure Coder が Trivy Package Scan 結果を code_scan として投入した経路である可能性が高い。第2回で触れた失敗経路がここでも確認できる。

HTTP 202 は「受け付けた」であって「処理が完了した」ではない。そのため、疎通確認では HTTP 202 の取得だけでなく、Event Log 上で「処理完了」まで確認することにした。Concert 側での非同期処理が完了するまで、ダッシュボードへの反映には少し時間がかかる。


dry-run で動作確認する

API 接続情報がない環境でも、--dry-run で送信内容を確認できる。

python scripts/concert_upload.py \
  --type package_sbom \
  --file concert-package-sbom.json \
  --dry-run

出力:

[INFO] === DRY RUN — no API call will be made ===
[INFO] Endpoint  : POST https://concert.example.com/ingestion/api/v1/upload_files
[INFO] InstanceId: 0000-0000-0000-0000
[INFO] Auth      : C_API_KEY <REDACTED>
[INFO] data_type : package_sbom
[INFO] metadata  : {"repo_url": "...", "repo_name": "...", "application_name": "..."}
[INFO] file      : concert-package-sbom.json (12345 bytes)
[INFO] === END DRY RUN ===

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

環境変数の設定: 最低限 CONCERT_BASE_URL と CONCERT_API_KEY が必要だ。GitHub Actions では Secrets に格納する。

CONCERT_BASE_URL=https://your-concert-host.example.com
CONCERT_API_KEY=<Concert UIで生成したAPIキー>
CONCERT_INSTANCE_ID=0000-0000-0000-0000
CONCERT_APPLICATION_NAME=your-application-name
CONCERT_APPLICATION_VERSION=1.0.0

.sarif 拡張子に注意: CodeQL の出力ファイルは .sarif だが、Concert は .json 拡張子しか受け付けない。アップロード時に拡張子を変換する処理が必要になる。

HTTP 202 は非同期受付: 202 が返っても、Concert のダッシュボードへの反映は即時ではない。Event Log で event_id を確認し、処理完了を待つ。

自己署名証明書を使っている検証環境では --insecure が必要な場合がある: TLS 検証をスキップする必要がある。本番環境では使用しない。


検証結果のまとめ

手動投入から REST API 投入に移行するために確認が必要だったことは3点だった。

  1. API が存在すること — IBM Docs と OpenAPI 仕様で確認した
  2. 3成果物すべてに対応する data_type があること — File Upload Matrix で確認した
  3. 実機で HTTP 202 が返ること — 疎通確認で確認した

API 仕様の調査段階では「実現可能性が高い」という評価にとどめていた。実機疎通で初めて「できる」と確認できた。

第2回では手動でファイルを投入していた。第3回では成果物を自動生成した。そして今回、API を使って投入そのものを自動化できることが確認できた。

残るのは、それらを GitHub Actions の中で結び付けることだ。


次回予告

今回は Upload Client をローカルで手動実行して API 疎通を確認した。

では、これを GitHub Actions に組み込んで main push のたびに自動実行するにはどうすればよいか。

第5回では concert-ingest ジョブを GitHub Actions ワークフローに追加し、コード変更からリスク可視化までを完全自動化した過程を記録する。


連載一覧

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

検証環境

  • IBM Concert: 3.0.x
  • Python: 3.12(標準ライブラリのみ使用)
  • concert_upload.py: stdlib のみ(urllib・json・argparse)
  • Trivy: 0.70.0(GitHub Actions 側)
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?