apisix 카탈로그 CVE 게이트 완전 해소 (차단 165건 → 0건)
etcd(bitnamilegacy 동결 미러) → etcd.enabled=false + 카탈로그 자체 etcd 차트를 externalEtcd 기본값으로 연결. adc·apisix-ingress-controller·apisix(paasup/apisix) 세 이미지는 SUSE BCI 자체 빌드로 교체 — 전부 벤더 등급만으로는 안 보이던 벤더 하향 등급 CVE(NVD 재평가 시 드러남)가 원인이었다. - images/apisix-ingress-controller: 정적 링크 Go 모듈 취약 버전만 강제 업그레이드 - images/apisix: APISIX-Runtime(WASM·dubbo 등 커스텀 모듈 포함) 전체를 SUSE BCI 위에서 소스로 재현, keycloak-authz 플러그인 오버레이 - images/adc: 업스트림 빌더 스테이지는 그대로 두고 distroless 최종 베이스만 SUSE BCI+nodejs24 로 교체 scripts/build/patch-catalog-tag.py 의 TAG_BLOCK 이 점 구분 중첩 경로를 지원하도록 확장(apisix 서브차트 alias 때문에 필요). 세 이미지 모두 게이트 PASS(실효 CRITICAL/HIGH 0/0)와 배포 검증(테스트 클러스터)을 마쳤다. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,11 @@ build-image.yml 이 호출한다. 카탈로그가 실제로 쓰는 두 표기
|
||||
텍스트 치환만 한다(YAML 파서를 쓰지 않는다) — 기존 주석·포매팅을 그대로 보존하기
|
||||
위함이다(기존 cnpg-cluster 워크플로의 sed 방식과 같은 원칙). 예상한 패턴을 하나도
|
||||
찾지 못하면 실패한다(조용히 건너뛰지 않는다) — 잘못된 치환보다 실패가 낫다.
|
||||
|
||||
--block 은 점으로 구분한 중첩 경로를 받는다(예: `ingress-controller.deployment.image` —
|
||||
apisix 서브차트처럼 image 블록이 top-level 이 아닌 경우). 첫 세그먼트만 진짜 top-level
|
||||
키로 요구하고(기존 단일 세그먼트 동작과 동일), 그다음 세그먼트부터는 앞 블록의 body
|
||||
안에서만 찾는다 — 다른 형제 블록에 있는 동명 키를 잘못 집지 않기 위함이다.
|
||||
"""
|
||||
import argparse
|
||||
import pathlib
|
||||
@@ -30,12 +35,53 @@ def read_image_name(text):
|
||||
return m.group(1) if m else None
|
||||
|
||||
|
||||
def read_split_block(text, block):
|
||||
block_re = re.compile(rf"^{re.escape(block)}:\n((?:[ \t]+.*\n?)*)", re.MULTILINE)
|
||||
m = block_re.search(text)
|
||||
def _locate_block(text, dotted_block, start=0, end=None, top_level=True):
|
||||
"""dotted_block(`a.b.c`) 를 순서대로 내려가며 최종 블록 body 의 (start, end) 절대
|
||||
오프셋을 text 안에서 찾는다. 첫 세그먼트는 top_level=True 라 들여쓰기 없는 줄에서만
|
||||
찾고(기존 단일 세그먼트 동작과 동일), 그 뒤 세그먼트는 앞 단계 body 범위 안에서만
|
||||
찾는다(들여쓰기는 실제 값으로 감지 — 파일마다 폭이 다를 수 있어 하드코딩하지 않는다)."""
|
||||
if end is None:
|
||||
end = len(text)
|
||||
segment, _, rest = dotted_block.partition(".")
|
||||
indent_group = r"" if top_level else r"([ \t]*)"
|
||||
pat = re.compile(rf"^{indent_group}{re.escape(segment)}:[ \t]*\n", re.MULTILINE)
|
||||
m = pat.search(text, start, end)
|
||||
if not m:
|
||||
return None
|
||||
body = m.group(1)
|
||||
indent = "" if top_level else m.group(1)
|
||||
body_start = m.end()
|
||||
# 더 들여쓰인 줄이 이어지는 동안 body 로 삼는다. 빈 줄(또는 공백만 있는 줄)은 그
|
||||
# 자체로는 블록을 끝내지 않는다 — 사람이 읽기 좋게 블록 중간에 문단 구분으로 넣는
|
||||
# 경우가 실제로 있다(업스트림 서브차트 values.yaml 등). 다만 뒤에 더 들여쓰인 줄이
|
||||
# 안 나오면(다음 블록으로 넘어가거나 파일 끝) 그 빈 줄들은 body 에서 잘라낸다 —
|
||||
# 아니면 다음 형제 블록과의 구분용 빈 줄까지 이 블록 소유로 잘못 삼키게 된다.
|
||||
line_re = re.compile(r"[^\n]*\n?")
|
||||
pos = body_start
|
||||
body_end = body_start
|
||||
while pos < end:
|
||||
lm = line_re.match(text, pos, end)
|
||||
line = lm.group(0)
|
||||
if not line:
|
||||
break
|
||||
content = line.rstrip("\n")
|
||||
if content.strip() == "":
|
||||
pos = lm.end()
|
||||
continue
|
||||
if not (content[: len(indent) + 1] == indent + " " or content[: len(indent) + 1] == indent + "\t"):
|
||||
break
|
||||
pos = lm.end()
|
||||
body_end = pos
|
||||
if not rest:
|
||||
return body_start, body_end
|
||||
return _locate_block(text, rest, body_start, body_end, top_level=False)
|
||||
|
||||
|
||||
def read_split_block(text, block):
|
||||
loc = _locate_block(text, block)
|
||||
if not loc:
|
||||
return None
|
||||
body_start, body_end = loc
|
||||
body = text[body_start:body_end]
|
||||
values = {}
|
||||
for name in ("registry", "repository", "tag"):
|
||||
fm = re.search(rf'^\s*{name}:\s*"?([^"\n]+?)"?\s*$', body, re.MULTILINE)
|
||||
@@ -63,14 +109,14 @@ def _sub_field(body, name, old_v, new_v):
|
||||
|
||||
|
||||
def patch_split_block(text, block, old, new):
|
||||
# block 시작 줄부터, 그보다 더 들여써진 줄이 이어지는 동안만 치환 대상으로 삼는다.
|
||||
# 다음 top-level(들여쓰기 없는) 키가 나오면 블록이 끝난 것으로 본다.
|
||||
block_re = re.compile(rf"^{re.escape(block)}:\n((?:[ \t]+.*\n?)*)", re.MULTILINE)
|
||||
m = block_re.search(text)
|
||||
if not m:
|
||||
# block(점 구분 중첩 경로 가능)이 시작하는 줄부터, 그보다 더 들여써진 줄이 이어지는
|
||||
# 동안만 치환 대상으로 삼는다. 그만큼 들여써지지 않은 줄이 나오면 블록이 끝난 것으로 본다.
|
||||
loc = _locate_block(text, block)
|
||||
if not loc:
|
||||
return None
|
||||
body_start, body_end = loc
|
||||
|
||||
body = m.group(1)
|
||||
body = text[body_start:body_end]
|
||||
changed = False
|
||||
|
||||
# registry 를 repository 와 분리된 필드로 쓰지 않는 차트가 있다(예: cloudnative-pg
|
||||
@@ -99,13 +145,13 @@ def patch_split_block(text, block, old, new):
|
||||
|
||||
if not changed:
|
||||
return None
|
||||
return text[: m.start(1)] + body + text[m.end(1) :]
|
||||
return text[:body_start] + body + text[body_end:]
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--style", required=True, choices=["imageName", "split"])
|
||||
ap.add_argument("--block", default="image", help="split 스타일일 때 대상 top-level 키")
|
||||
ap.add_argument("--block", default="image", help="split 스타일일 때 대상 키(점 구분 중첩 경로 가능, 예: ingress-controller.deployment.image)")
|
||||
ap.add_argument("--read", metavar="FILE", help="현재 태그만 읽어 출력하고 종료 (patch 안 함)")
|
||||
ap.add_argument("--old", help="이전 전체 태그 (registry/repo:tag) — --read 아닐 때 필수")
|
||||
ap.add_argument("--new", help="새 전체 태그 (registry/repo:tag) — --read 아닐 때 필수")
|
||||
|
||||
Reference in New Issue
Block a user