scan-sbom.sh: CoverageProbe 커버리지 자가진단 이식 + 관련 파이프라인 버그 수정

security-catalog 프로젝트에서 자체 빌드 이미지 3종을 실제로 로컬 빌드·게이트
검증하는 과정에서 발견한 문제들:

- extract-helm-images.sh 가 `image:` 필드만 잡고 `imageName:`(CNPG Cluster CRD 관례)
  은 놓쳐, cnpg-cluster 차트의 이미지가 SBOM·스캔·게이트 어디에도 안 나타났다.
- patch-catalog-tag.py 의 split 포맷 패처가 registry 필드가 따로 없고 repository 에
  registry+repo 를 합쳐 쓰는 차트(cloudnative-pg 오퍼레이터, 업스트림 템플릿이
  image.registry 를 아예 참조하지 않음)에서 tag 만 조용히 갱신하고 repository 는
  그대로 남겨 깨진 참조를 만들 수 있었다.
- CoverageProbe(센티널 패키지 주입 재스캔으로 "0건"과 "측정 안 됨"을 구분)가
  이식되지 않아, 전 심각도 0건인 자체 빌드 이미지가 실제로는 깨끗한데도 게이트가
  "데이터 커버리지 이상"으로 오탐 처리했다 — security-catalog 의 scan-sbom.sh 를
  이식해 해소. cve-gate.py 는 이미 이 키를 읽도록 구현돼 있어 소비 쪽 변경은 없다.

rpm(SUSE)·deb(Debian)·apk(Alpine) 세 센티널 경로와 병렬 스캔 회귀를 로컬에서
확인했고, 이식 후 cloudnative-pg·cnpg-postgresql 게이트가 실제로 FAIL→PASS 로
바뀌는 것도 확인했다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wbsong111
2026-08-03 15:30:57 +09:00
parent 6878b61efa
commit 6597059a15
6 changed files with 159 additions and 35 deletions
+34 -15
View File
@@ -52,10 +52,17 @@ def patch_image_name(text, old, new):
return text.replace(pattern, f'imageName: "{new}"')
def patch_split_block(text, block, old, new):
old_registry, old_repo, old_tag = split_tag(old)
new_registry, new_repo, new_tag = split_tag(new)
def _sub_field(body, name, old_v, new_v):
"""`name:` 필드의 값을 old_v -> new_v 로 치환한다. 값에 따옴표가 있었으면 유지하고
없었으면 안 붙인다(원본 포매팅 보존 원칙 — 모듈 docstring 참고)."""
if not old_v:
return body, False
pattern = re.compile(rf'(^\s*{name}:\s*)("?){re.escape(old_v)}\2(?=\s*$)', re.MULTILINE)
new_body, n = pattern.subn(lambda mo: f"{mo.group(1)}{mo.group(2)}{new_v}{mo.group(2)}", body)
return (new_body, True) if n else (body, False)
def patch_split_block(text, block, old, new):
# block 시작 줄부터, 그보다 더 들여써진 줄이 이어지는 동안만 치환 대상으로 삼는다.
# 다음 top-level(들여쓰기 없는) 키가 나오면 블록이 끝난 것으로 본다.
block_re = re.compile(rf"^{re.escape(block)}:\n((?:[ \t]+.*\n?)*)", re.MULTILINE)
@@ -65,18 +72,30 @@ def patch_split_block(text, block, old, new):
body = m.group(1)
changed = False
for name, old_v, new_v in (
("registry", old_registry, new_registry),
("repository", old_repo, new_repo),
("tag", old_tag, new_tag),
):
if not old_v:
continue
field_re = re.compile(rf'(^\s*{name}:\s*)"{re.escape(old_v)}"', re.MULTILINE)
new_body, n = field_re.subn(rf'\1"{new_v}"', body)
if n:
body = new_body
changed = True
# registry 를 repository 와 분리된 필드로 쓰지 않는 차트가 있다(예: cloudnative-pg
# 오퍼레이터 — 업스트림 템플릿이 `.Values.image.registry` 를 아예 참조하지 않고
# `repository` 하나에 registry/네임스페이스까지 전부 담아 쓴다, 실측으로 확인).
# 이런 차트에서 registry 를 억지로 분리해 치환하면 실제 배포에 쓰이는 repository
# 필드는 그대로 남고 tag 만 바뀌어 깨진 이미지 참조를 만든다 — block 에 registry:
# 필드가 실제로 있는지 먼저 확인해 분기한다.
has_registry_field = re.search(r"^\s*registry:\s*\S", body, re.MULTILINE) is not None
if has_registry_field:
old_registry, old_repo, old_tag = split_tag(old)
new_registry, new_repo, new_tag = split_tag(new)
body, c = _sub_field(body, "registry", old_registry, new_registry)
changed = changed or c
body, c = _sub_field(body, "repository", old_repo, new_repo)
changed = changed or c
else:
old_repo_full, old_tag = old.rsplit(":", 1)
new_repo_full, new_tag = new.rsplit(":", 1)
body, c = _sub_field(body, "repository", old_repo_full, new_repo_full)
changed = changed or c
body, c = _sub_field(body, "tag", old_tag, new_tag)
changed = changed or c
if not changed:
return None