From 2417655987b603007e5ba6485db4c87f4b164cc8 Mon Sep 17 00:00:00 2001 From: wbsong111 Date: Thu, 27 Aug 2026 08:41:16 +0900 Subject: [PATCH] =?UTF-8?q?apisix=EC=9D=98=20keycloak-authz=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=20=ED=83=9C=EA=B7=B8=EB=A5=BC=20published.js?= =?UTF-8?q?on=20=ED=91=9C=EC=A4=80=20=EA=B2=BD=EB=A1=9C=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=EB=A1=9C=20=EB=B0=98=EC=98=81=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apisix에 얹는 keycloak-authz 커스텀 플러그인 오버레이는 hardened-containers 표준 파이프라인 밖에서 별도로 빌드돼 같은 리포지토리에 -keycloak-authz 접미사를 붙여 추가로 push된다. published.json에는 이 접미사 태그가 절대 안 잡혀서, apply-published-tags.py가 무필터로 ref를 그대로 적용하는 지금 방식으로는 카탈로그가 순정(keycloak-authz 없는) 태그를 가리키게 되는 실질적 회귀가 있었다 (manifests/helm/apisix/2.17.0/custom-values.yaml이 실제로 이 상태였다). scripts/build/resolve-apisix-keycloak-authz-tag.py를 새로 만들어 apply-published-tags.py가 읽기 전에 published.json 사본의 apisix 항목을 제자리에서 고쳐 쓴다 — base 태그에 -keycloak-authz 버전이 실제로 있으면 그걸로 교체하고, 없으면 apisix 항목 자체를 지워 표준 "없다" 분기가 자연히 건너뛰게 한다(순정 태그로 되돌리는 일은 없다). apply-published-tags.py는 한 줄도 안 건드렸다. catalog-tag-update.yml에 이 전처리를 한 스텝만 끼워 넣고, 즉시 1회 실행해 2.17.0의 회귀를 이 커밋에서 바로 고쳤다(3.18.0-security-hardened-20260826 → ...-keycloak-authz). Co-Authored-By: Claude Sonnet 5 --- .github/workflows/catalog-tag-update.yml | 9 ++ catalog/image-map/README.md | 17 +++ catalog/image-map/apisix.env | 6 + .../helm/apisix/2.17.0/custom-values.yaml | 2 +- .../resolve-apisix-keycloak-authz-tag.py | 129 ++++++++++++++++++ 5 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 scripts/build/resolve-apisix-keycloak-authz-tag.py diff --git a/.github/workflows/catalog-tag-update.yml b/.github/workflows/catalog-tag-update.yml index 2fe5870..9548571 100644 --- a/.github/workflows/catalog-tag-update.yml +++ b/.github/workflows/catalog-tag-update.yml @@ -43,6 +43,15 @@ jobs: -o /tmp/published.json python3 -c "import json; json.load(open('/tmp/published.json'))" # 스키마 최소 확인 + # apisix에 얹는 keycloak-authz 커스텀 플러그인 오버레이는 hardened-containers + # 표준 파이프라인 밖에서 별도로 빌드돼 같은 리포지토리에 -keycloak-authz 접미사로 + # 추가 push된다 — published.json에는 이 접미사 태그가 절대 안 잡힌다. 이 스텝이 + # /tmp/published.json의 apisix 항목을 제자리에서 고쳐, 아래 "반영 대상 계산"이 + # 그걸 원래 발행된 태그인 것처럼 그대로 적용하게 한다. 상세: + # scripts/build/resolve-apisix-keycloak-authz-tag.py 모듈 docstring. + - name: apisix keycloak-authz 변형 확인 + run: python3 scripts/build/resolve-apisix-keycloak-authz-tag.py --published /tmp/published.json + - name: 반영 대상 계산 id: apply run: | diff --git a/catalog/image-map/README.md b/catalog/image-map/README.md index e1056d7..364157c 100644 --- a/catalog/image-map/README.md +++ b/catalog/image-map/README.md @@ -34,3 +34,20 @@ hardened-containers 가 알아야 하므로, 레포 분리 시 이 지식을 카 여러 버전을 **의도적으로 병행 추적**해야 하는 예외적 케이스(같은 이미지를 쓰는 두 메이저 라인을 동시에 지원해야 하는 경우 등)만 공백으로 여러 디렉토리를 나열한다 — 이 경우 왜 병행이 필요한지 `CUSTOM-README.md`나 커밋 메시지에 근거를 남긴다. + +## 발행 경로가 published.json 하나로 안 끝나는 이미지 + +원칙은 "`published.json`의 `ref`를 그대로 믿고 반영한다"지만, 일부 이미지는 +hardened-containers의 표준 파이프라인 밖에서 만들어져 `published.json`에 안 +잡힌다. 현재 유일한 사례: **apisix의 keycloak-authz 커스텀 플러그인 오버레이** +— 표준 빌드 뒤에 별도 프로세스가 같은 리포지토리에 `-keycloak-authz` 접미사를 +붙여 추가로 push한다. + +이런 이미지는 `published.json`을 그대로 신뢰하지 않고, `catalog-tag-update.yml`이 +그 이미지 전용 전처리 스크립트로 published.json 사본을 먼저 고쳐 쓴 뒤에야 표준 +반영 로직(`apply-published-tags.py`)이 돈다 — apisix의 경우 +`scripts/build/resolve-apisix-keycloak-authz-tag.py`. **원하는 태그가 실제로 +존재할 때만 대체하고, 없으면 그 이미지를 이번 실행에서 건너뛴다** — 접미사 없는 +순정 태그로 되돌리는 일은 없다(순정으로 돌아가면 그 이미지가 실제로 요구하는 +커스텀 기능이 빠지는 회귀가 된다). 다음에 비슷한 예외가 생기면 이 패턴을 +참고한다. diff --git a/catalog/image-map/apisix.env b/catalog/image-map/apisix.env index 1a81e63..93d1117 100644 --- a/catalog/image-map/apisix.env +++ b/catalog/image-map/apisix.env @@ -1,3 +1,9 @@ +# keycloak-authz 커스텀 플러그인 오버레이는 hardened-containers 표준 파이프라인 +# 밖에서 별도로 빌드돼 같은 리포지토리에 -keycloak-authz 접미사로 추가 push된다 — +# published.json에는 이 접미사 태그가 안 잡힌다. catalog-tag-update.yml이 +# scripts/build/resolve-apisix-keycloak-authz-tag.py로 published.json 사본을 +# 먼저 고쳐 쓴 뒤에야 아래 CHART_DIRS/TAG_STYLE/TAG_BLOCK 기준 표준 반영 로직이 +# 이 이미지에 대해 돈다. 상세: catalog/image-map/README.md. CHART_DIRS="manifests/helm/apisix/2.17.0" TAG_STYLE=split TAG_BLOCK=image diff --git a/manifests/helm/apisix/2.17.0/custom-values.yaml b/manifests/helm/apisix/2.17.0/custom-values.yaml index 73513f7..10cbd4a 100644 --- a/manifests/helm/apisix/2.17.0/custom-values.yaml +++ b/manifests/helm/apisix/2.17.0/custom-values.yaml @@ -12,7 +12,7 @@ # 자체는 그대로 둠). image: repository: docker.io/paasup/apisix - tag: "3.18.0-security-hardened-20260826" + tag: "3.18.0-security-hardened-20260826-keycloak-authz" pullPolicy: IfNotPresent replicaCount: 1 diff --git a/scripts/build/resolve-apisix-keycloak-authz-tag.py b/scripts/build/resolve-apisix-keycloak-authz-tag.py new file mode 100644 index 0000000..d73a66b --- /dev/null +++ b/scripts/build/resolve-apisix-keycloak-authz-tag.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +"""resolve-apisix-keycloak-authz-tag.py — apisix의 keycloak-authz 커스텀 플러그인 +오버레이 태그를 published.json에 반영한다(제자리 수정). + +apisix에 얹는 keycloak-authz 오버레이는 hardened-containers의 표준 파이프라인 +밖에서 별도로 빌드돼, 같은 `docker.io/paasup/apisix` 리포지토리에 표준 빌드보다 +나중에 `-keycloak-authz` 접미사를 붙여 추가로 push된다. hardened-containers의 +`published.json`에는 이 접미사 태그가 절대 안 잡힌다(그 레포가 만드는 게 아니므로). + +이 스크립트는 `apply-published-tags.py`가 published.json을 읽기 *전에* 끼어들어 +`images.apisix` 항목을 제자리에서 고쳐 쓴다: + + 1. published.json의 apisix base 태그(`-security-hardened-`)를 읽는다. + 2. `-keycloak-authz`가 Docker Hub에 실제로 존재하는지 확인한다. + 3. 있으면: `images.apisix`를 그 접미사 태그로 교체한다 — 이후 + apply-published-tags.py는 이걸 원래 발행된 태그인 것처럼 그대로 적용한다. + 4. 없으면: `images.apisix` 항목 자체를 지운다 — apply-published-tags.py의 기존 + "published.json에 없다" 분기가 자연히 타서 apisix를 건너뛴다. **순정(접미사 + 없는) 태그로 되돌리는 일은 없다** — keycloak-authz가 빠지면 apisix가 실제로 + 요구하는 커스텀 인증 플러그인 없이 배포되는 회귀이기 때문이다. + +apply-published-tags.py·apply_for_image()는 건드리지 않는다 — 이 스크립트가 +끝난 뒤의 published.json이 "원래 그런 내용이었던 것"처럼 보이게만 한다. + +사용 +---- + python3 scripts/build/resolve-apisix-keycloak-authz-tag.py --published /tmp/published.json + +종료 코드 + 0 정상(교체·삭제·무변경 무관) + 2 실행 오류(published.json 파싱 실패 등 — Docker Hub 조회 실패는 포함 안 함, + 아래 참고) +""" +import argparse +import json +import sys +import urllib.error +import urllib.request +from pathlib import Path + + +def fetch_tags(repo): + """Docker Hub 공개 API로 /의 태그 전체를 가져온다. + {name: {tag_last_pushed, digest}} 로 반환.""" + tags = {} + url = f"https://hub.docker.com/v2/repositories/{repo}/tags?page_size=100" + while url: + req = urllib.request.Request(url, headers={"Accept": "application/json"}) + with urllib.request.urlopen(req, timeout=30) as resp: + doc = json.load(resp) + for r in doc.get("results") or []: + name = r.get("name") + if not name: + continue + tags[name] = { + "tag_last_pushed": r.get("tag_last_pushed"), + "digest": r.get("digest"), + } + url = doc.get("next") + return tags + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--published", required=True, metavar="FILE", + help="hardened-containers의 published.json — 제자리에서 수정한다") + ap.add_argument("--repo", default="paasup/apisix", + help="Docker Hub 리포지토리 (namespace/repo, 기본 paasup/apisix)") + ap.add_argument("--suffix", default="-keycloak-authz", + help="찾을 태그 접미사 (기본 -keycloak-authz)") + ap.add_argument("--registry", default="docker.io", + help="레지스트리 호스트 (기본 docker.io)") + args = ap.parse_args() + + published_path = Path(args.published) + try: + doc = json.loads(published_path.read_text()) + except Exception as e: + print(f"::error::published.json 을 읽지 못했다: {e}", file=sys.stderr) + return 2 + + images = doc.get("images") or {} + entry = images.get("apisix") + if entry is None: + print("apisix: published.json 에 없다 — 건너뛴다") + return 0 + if entry.get("gate") != "pass": + print(f"apisix: gate={entry.get('gate')!r} — 건너뛴다") + return 0 + + base_tag = entry.get("tag") + if not base_tag: + print("::warning::apisix: published.json 항목에 tag 가 없다 — 건너뛴다", + file=sys.stderr) + return 0 + + candidate = f"{base_tag}{args.suffix}" + + try: + tags = fetch_tags(args.repo) + except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as e: + print(f"::warning::apisix: Docker Hub 태그 조회 실패({e}) — " + f"이번 실행에서는 기존 published.json 항목({base_tag})을 그대로 둔다", + file=sys.stderr) + return 0 + + if candidate in tags: + found = tags[candidate] + images["apisix"] = { + "ref": f"{args.registry}/{args.repo}:{candidate}", + "tag": candidate, + "digest": found.get("digest") or entry.get("digest"), + "gate": "pass", + } + published_path.write_text(json.dumps(doc)) + print(f"apisix: keycloak-authz 변형 발견 — {candidate} 로 대체") + else: + del images["apisix"] + published_path.write_text(json.dumps(doc)) + print(f"::warning::apisix: {candidate} 가 아직 없다 — " + f"이번 실행에서 apisix 를 건너뛴다(순정 태그로 되돌리지 않는다)", + file=sys.stderr) + + return 0 + + +if __name__ == "__main__": + sys.exit(main())