자체 빌드 드리프트 스캔·재빌드 트리거를 제거하고 CI 파이프라인을 정리한다

hardened-containers가 이미 rescan.yml로 매일 자율 재스캔·재빌드하고, sbom.yml이
custom-values.yaml 기준으로 자체 빌드 이미지를 다른 카탈로그 이미지와 동일하게
스캔하고 있어 self-build-drift-check.yml의 트리거·전용 스캔이 순수 중복이었다
(SECURITY_IMAGES_DISPATCH_TOKEN도 등록된 적 없어 트리거 스텝은 항상 실패하던
죽은 코드). check-rebuild-needed.py가 더하던 fixable/no-fix 구분도 cve-gate.py
리포트에 이미 있어 흡수할 필요 없이 삭제했다. 근거는 ADR 0005.

곁들여 CI 위생 문제(trivy DB 캐시 없음, concurrency 없음, catalog-tag-update.yml의
브랜치 누적)를 함께 고치고, hardened-containers의 docs/image-authoring.md가
docs/image-authoring/ 로 분할된 것을 반영해 관련 링크를 정정했다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wbsong111
2026-08-26 09:48:48 +09:00
parent 9e5b6633db
commit f5b8e4b91f
20 changed files with 339 additions and 452 deletions
+15 -4
View File
@@ -6,7 +6,7 @@ name: catalog-tag-update
# 차트에 반영할지" 는 이 카탈로그가 판단한다(catalog/image-map/<image>.env).
#
# public raw URL 로 읽으므로 인증이 필요 없다. hardened-containers 쪽 시크릿·PAT 도 없다 —
# 의존 방향은 카탈로그 → 이미지 단방향이다(docs/image-authoring.md "카탈로그 레포와의
# 의존 방향은 카탈로그 → 이미지 단방향이다(docs/image-authoring/ci.md "카탈로그 레포와의
# 계약", hardened-containers 레포).
#
# PR 은 자동 생성하지 않는다 — GitHub Actions 는 GITHUB_TOKEN 으로 PR 을 만들 수 없다는
@@ -22,6 +22,12 @@ on:
permissions:
contents: write
concurrency:
group: catalog-tag-update
# push 도중 취소를 막는다 — 아래 고정 브랜치(build/catalog-tag-update)를 두고
# 두 실행이 경합하는 것을 방지한다.
cancel-in-progress: false
env:
SECURITY_IMAGES_REPO: paasup/hardened-containers
@@ -51,10 +57,15 @@ jobs:
if: steps.apply.outputs.count != '0'
run: |
set -euo pipefail
BRANCH="build/catalog-tag-update-$(date -u +%Y%m%d%H%M%S)"
# 고정 브랜치명 하나를 재사용한다 — PR 은 사람이 열어야 해서(조직 정책) 매 실행
# 새 브랜치를 만들면 방치된 동일 내용 브랜치가 날마다 쌓인다. 이 브랜치는 이
# 워크플로만 쓰므로 force push 가 안전하다. 이미 이 브랜치로 PR 이 열려 있으면
# force push 가 그 PR 을 갱신한다(원하는 동작) — PR 이 머지된 뒤면 다음 실행이
# main 기준으로 새로 만든다.
BRANCH="build/catalog-tag-update"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git checkout -B "$BRANCH"
# apply-published-tags.py 가 이미 파일을 고쳤다 — 여기서는 그 결과를 커밋만 한다.
FILES="$(python3 -c "
@@ -74,7 +85,7 @@ jobs:
-m "hardened-containers 레포의 published.json(게이트 PASS + push 확인된 발행 기록)을" \
-m "반영한다. 상세는 이 실행의 Job Summary 참고." \
-m "Co-Authored-By: github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push -u origin "$BRANCH"
git push --force origin "HEAD:$BRANCH"
COMPARE_URL="https://github.com/${{ github.repository }}/compare/main...${BRANCH}?expand=1"
{
+18
View File
@@ -32,6 +32,10 @@ on:
permissions:
contents: read
concurrency:
group: cve-edge-post
cancel-in-progress: false # 외부로 POST 하는 잡이라 중간 취소보다 직렬화가 안전하다
jobs:
cve-json:
runs-on: ubuntu-latest
@@ -57,6 +61,20 @@ jobs:
- name: git safe.directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: 캐시 키 날짜 계산
id: cache-date
run: echo "date=$(date -u +%Y%m%d)" >> "$GITHUB_OUTPUT"
# trivy 취약점 DB 캐시. sbom.yml 과 같은 규칙 — 하루 단위 키, 실패해도 무시.
- name: trivy DB 캐시
continue-on-error: true
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/sbom-out/cache
key: trivy-db-${{ steps.cache-date.outputs.date }}
restore-keys: |
trivy-db-
# 사설 레지스트리 인증: 시크릿으로 docker config.json 을 만들어 trivy 가 읽게 한다.
- name: 레지스트리 인증 구성
env:
+24
View File
@@ -26,6 +26,12 @@ on:
permissions:
contents: read
concurrency:
group: sbom-${{ github.ref }}
# PR 증분 diff 는 base_ref...HEAD 기준이라 최신 실행이 이전 커밋들의 변경까지 모두
# 포함한다 — 취소해도 누락이 없다. 스케줄 전체 스캔은 취소하지 않는다.
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
sbom:
runs-on: ubuntu-latest
@@ -58,6 +64,24 @@ jobs:
- name: git safe.directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: 캐시 키 날짜 계산
id: cache-date
run: echo "date=$(date -u +%Y%m%d)" >> "$GITHUB_OUTPUT"
# trivy 취약점 DB 캐시. 하루 단위 키라 같은 날 여러 번 돌아도 최초 1회만
# 새로 받는다. DB 가 낡아도 trivy 가 스스로 갱신하므로 오래된 캐시(전날 키,
# restore-keys 로 폴백)를 복원해도 정확성 문제는 없다 — 다운로드 분량만
# 줄어든다. 캐시는 최적화이지 정확성 요건이 아니므로 실패해도 워크플로를
# 막지 않는다.
- name: trivy DB 캐시
continue-on-error: true
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/sbom-out/cache
key: trivy-db-${{ steps.cache-date.outputs.date }}
restore-keys: |
trivy-db-
# 사설 레지스트리 인증: 시크릿으로 docker config.json 을 만들어 trivy 가 읽게 한다.
- name: 레지스트리 인증 구성
env:
@@ -1,83 +0,0 @@
name: self-build-drift-check
# 배포 중인 자체 빌드 이미지(hardened-containers 레포가 만든 것)가 새 CVE 로 규정을 벗어났는지
# 이 카탈로그가 스스로 스캔해 판정한다. "무엇이 배포 중인가"는 이 카탈로그만 안다 —
# hardened-containers 는 자기 스스로 이 판단을 하지 않는다
# (hardened-containers 레포 docs/image-authoring.md "재빌드는 이 레포가 스스로 트리거하지 않는다").
#
# 판정(scripts/build/check-rebuild-needed.py)이 재빌드 대상이라고 하면 hardened-containers 의
# `build-image.yml` 을 `workflow_dispatch` 로 부른다. 그 워크플로가 실제 빌드·검증·게이트·
# push·`published.json` 갱신을 한다 — 이 워크플로는 트리거만 한다.
#
# 레지스트리 push 와 카탈로그 반영은 이 워크플로가 하지 않는다. `catalog-tag-update.yml` 이
# `published.json` 을 읽어가는 별도 경로로 반영한다.
on:
workflow_dispatch:
schedule:
- cron: '0 2 * * 1' # 매주 월요일 02:00 UTC
env:
SECURITY_IMAGES_REPO: paasup/hardened-containers
jobs:
drift-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: trivy 설치
run: |
curl -fsSL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
| sh -s -- -b /usr/local/bin
trivy --version | head -1
- name: 배포 중인 자체 빌드 이미지 스캔
id: scan
run: |
set -uo pipefail
mkdir -p /tmp/drift-reports
python3 scripts/build/check-rebuild-needed.py --list-refs > /tmp/refs.tsv
while IFS=$'\t' read -r img ref fname; do
[ -n "${fname:-}" ] || continue
echo "스캔: $ref"
trivy image --quiet --scanners vuln \
--severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL \
--format json "$ref" > "/tmp/drift-reports/$fname" \
|| { echo "::warning::$img: 스캔 실패 ($ref) — 이 이미지는 판정에서 빠진다"
rm -f "/tmp/drift-reports/$fname"; }
done < /tmp/refs.tsv
python3 scripts/build/check-rebuild-needed.py \
--reports /tmp/drift-reports \
--summary-md /tmp/drift.md \
--json-out /tmp/drift.json
cat /tmp/drift.md >> "$GITHUB_STEP_SUMMARY"
python3 - <<'PY' >> "$GITHUB_OUTPUT"
import json
rows = json.load(open("/tmp/drift.json"))
print("images=" + json.dumps([r["image"] for r in rows if r["status"] == "rebuild"]))
PY
- name: hardened-containers 재빌드 트리거
if: steps.scan.outputs.images != '' && steps.scan.outputs.images != '[]'
env:
GH_TOKEN: ${{ secrets.SECURITY_IMAGES_DISPATCH_TOKEN }}
run: |
set -euo pipefail
[ -n "$GH_TOKEN" ] || { echo "::error::SECURITY_IMAGES_DISPATCH_TOKEN 시크릿 없음 — ${SECURITY_IMAGES_REPO} 에 workflow_dispatch 를 걸 권한이 있는 PAT 필요"; exit 1; }
python3 -c "import json,sys; print('\n'.join(json.loads(sys.argv[1])))" '${{ steps.scan.outputs.images }}' | \
while read -r img; do
[ -n "$img" ] || continue
echo "재빌드 트리거: $img"
gh workflow run self-build-image --repo "$SECURITY_IMAGES_REPO" -f "image=$img"
done
{
echo
echo "재빌드를 트리거했다: ${{ steps.scan.outputs.images }}"
echo "진행 상황: https://github.com/${SECURITY_IMAGES_REPO}/actions/workflows/self-build-image.yml"
echo
echo "게이트 PASS + push 성공 시 그 레포의 \`published.json\` 이 갱신되고,"
echo "\`catalog-tag-update.yml\` 이 다음 실행에서 카탈로그에 반영한다."
} >> "$GITHUB_STEP_SUMMARY"