sbom-ci: meaningful stage timings, dynamic severity cols, doc update

- scan-sbom: 요약에 단계별 소요(SBOM 생성 vs 취약점 스캔) 표시, 오해 주던 "총 소요"·
  항상 0인 per-SBOM Sec·min/max 라인 제거. 스캔한 SEVERITY 만 동적 컬럼.
- generate-sbom: 생성 소요시간을 .sbom-gen-seconds 로 기록(요약 단계별 시간용).
- doc/sbom-pipeline.md: 실행 이미지(Dockerfile)·빌드/푸시·GitHub 설정·결과 확인/대응 보강.
- .gitignore: .sbom-gen-seconds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
wbsong111
2026-07-08 14:29:07 +09:00
parent f8ca673630
commit c36edb3015
4 changed files with 134 additions and 49 deletions
+15 -11
View File
@@ -134,11 +134,15 @@ xargs -P "$PARALLEL" -I{} bash -c '
>> "$RESULTS"
echo ">> [$(ts)] 스캔 완료 ($(fmt_elapsed $(( $(date +%s) - P1 ))))" | tee -a "$LOG" >&2
TOTAL=$(( $(date +%s) - RUN_START ))
python3 - "$RESULTS" "$CHARTMAP" "$SEVERITY" "$TOTAL" "$(ts)" "$SUMMARY_TSV" <<'PY' | tee "$SUMMARY_MD" >&2
TOTAL=$(( $(date +%s) - RUN_START )) # 취약점 스캔 단계(오프라인) 소요
GEN_SECS=$(cat "$OUT_DIR/.sbom-gen-seconds" 2>/dev/null || echo "") # SBOM 생성 단계(이미지 pull) 소요
python3 - "$RESULTS" "$CHARTMAP" "$SEVERITY" "$TOTAL" "$(ts)" "$SUMMARY_TSV" "${GEN_SECS:-}" <<'PY' | tee "$SUMMARY_MD" >&2
import sys
results, chartmap, sev, total, tstamp, tsv_out = sys.argv[1:7]
total=int(total)
gen = sys.argv[7] if len(sys.argv)>7 and sys.argv[7].strip().isdigit() else None
gen = int(gen) if gen is not None else None
def dur(s): return f"{s//60}분 {s%60}초"
# 표시 심각도 = 실제 스캔한 SEVERITY 만 (순위 내림차순). MED/LOW 는 스캔 안 하면 항상 0이므로 제외.
RANK=["CRITICAL","HIGH","MEDIUM","LOW"]; IDX={"CRITICAL":1,"HIGH":2,"MEDIUM":3,"LOW":4}
scanned=[s.strip().upper() for s in sev.split(",") if s.strip()]
@@ -162,25 +166,25 @@ def cnt(r,s):
rows.sort(key=lambda r: tuple(-cnt(r,s) for s in shown))
ok=[r for r in rows if r[5]=="OK"]; err=[r for r in rows if r[5]!="OK"]
totals={s:sum(cnt(r,s) for r in ok) for s in shown}
secs=[int(r[6]) for r in rows if len(r)>=7 and r[6].isdigit()]
# TSV: chart, image, <shown counts...>, status, sec (CRITICAL 은 첫 카운트 열 = col3)
# TSV: chart, image, <shown counts...>, status (CRITICAL 은 첫 카운트 열 = col3)
with open(tsv_out,"w") as f:
for r in rows:
cols=[cm.get(r[0],""), r[0]] + [str(cnt(r,s)) for s in shown] + [r[5], r[6] if len(r)>=7 else ""]
cols=[cm.get(r[0],""), r[0]] + [str(cnt(r,s)) for s in shown] + [r[5]]
f.write("\t".join(cols)+"\n")
print("# Trivy SBOM 취약점 요약\n")
print(f"- 생성: {tstamp}")
print(f"- 총 소요: **{total//60}분 {total%60}초** (SBOM {len(rows)}개)")
print(f"- 성공/실패: **{len(ok)} / {len(err)}**")
print(f"- 대상 SBOM: **{len(rows)}개** (성공 {len(ok)} / 실패 {len(err)})")
if gen is not None:
print(f"- SBOM 생성 소요: **{dur(gen)}** (이미지 pull 포함, 파이프라인 주 비용)")
print(f"- 취약점 스캔 소요: **{dur(total)}** (오프라인, SBOM 입력)")
print(f"- 집계 심각도: `{sev}`")
print("- 취약점 합계: " + ", ".join(f"**{s} {totals[s]}**" for s in shown))
if secs: print(f"- SBOM당 스캔시간: 최소 {min(secs)}s / 최대 {max(secs)}s / 평균 {sum(secs)//len(secs)}s")
print()
print("| Chart | Image | " + " | ".join(shown) + " | Status | Sec |")
print("|---|---|" + "---:|"*len(shown) + "---|---:|")
print("| Chart | Image | " + " | ".join(shown) + " | Status |")
print("|---|---|" + "---:|"*len(shown) + "---|")
for r in rows:
cells=" | ".join(str(cnt(r,s)) for s in shown)
print(f"| {cm.get(r[0],'')} | `{r[0]}` | {cells} | {r[5]} | {r[6] if len(r)>=7 else '-'} |")
print(f"| {cm.get(r[0],'')} | `{r[0]}` | {cells} | {r[5]} |")
PY
{