argocd: 자체 빌드 하드닝 이미지 추가, 게이트 차단 0건
업스트림 quay.io/argoproj/argocd 의 차단 CVE 는 대부분 OS 패키지가 아니라 바이너리에
정적 링크된 Go 모듈이라, 상위 태그 교체(이미 최신)로도 베이스 OS 교체로도 잡히지 않는다.
부분 조치는 효과가 없었다 — 같은 stdlib·x/crypto 취약점이 다섯 바이너리(argocd·helm·
kustomize·git-lfs·pebble)에 각각 들어있어 본체만 고치면 거의 줄지 않는다. 업스트림
Dockerfile 이 helm/kustomize/git-lfs 를 hack/install.sh 로 **미리 빌드된 릴리스 바이너리**
로 내려받기 때문에 그 바이너리의 Go 버전을 우리가 통제할 수도 없다. 그래서 넷 다 소스에서
다시 컴파일한다.
- images/argocd/ 신규 (Go 4개 + C 2개 + Node UI — 기존 자체 빌드 중 가장 크다)
- 최종 베이스 ubuntu → SUSE BCI(원칙 2). ubuntu 가 딸려오던 pebble 도 함께 사라진다
- tini·connect-proxy 는 SLE_BCI 에 없어 소스 빌드 — 기능을 빼지 않기 위해
- UI 는 업스트림과 동일하게 node 로 빌드(Go embed 라 생략 불가)
- manifests/helm/argo-cd/10.4.0/custom-values.yaml 이 이 이미지를 가리킨다
global.image 하나로 argocd 바이너리를 쓰는 5개 컴포넌트가 공유한다
재사용 설계 — Dockerfile 에 버전을 박지 않는다
---------------------------------------------
이 이미지는 앞으로도 CVE 조치를 반복해서 받는다. 모듈 목록·패키지 목록을 전부 값으로 빼서
다음 조치에서 바뀌는 것이 source.build.env 한 곳뿐이게 했다. 이전 방식이라면 모듈 하나
추가에 build.env·ARG 선언·go get 목록·BUILD_ARGS 네 곳을 고쳐야 했다.
- scripts/build/suggest-go-upgrades.py 신규 — 스캔 리포트의 FixedVersion 에서
GO_MODULE_UPGRADES / GO_BUILDER_TAG 를 산출한다. 사람이 CVE 를 훑어 최대값을 고르지
않는다. 빌드 시점에 최신을 당기는 방식(go get -u)은 재현성을 버리므로 택하지 않았다 —
값은 제안만 하고 채택은 사람이 커밋한다.
- images/argocd/go-mod-upgrade.sh 신규 — 목록 하나를 네 프로젝트에 재사용한다.
go get 은 의존성에 없는 모듈도 go.mod 에 추가하므로 그래프에 있는 것만 골라 적용한다.
실측 함정 — verify.sh 에 반영했다
--------------------------------
차트의 repo-server init 컨테이너가 `cp --update=none` 을 쓴다. 이 형식은 GNU coreutils
9.3+ 에서만 되는데, BCI 15.7 로 빌드한 이미지가 게이트도 verify.sh 도 통과하고 **배포
시점에** Init:CrashLoopBackOff 로 죽었다. BCI 16.0(coreutils 9.6)으로 올려 해소했고,
verify.sh 가 이 명령과 copyutil 흐름을 직접 재현하도록 해서 다음엔 빌드 단계에서 걸린다.
16.0 의 패키지 가용성과 trivy 커버리지(CoverageProbe=ok, EOSL 아님)도 확인했다.
검증
----
게이트 PASS — 실효 CRITICAL/HIGH 0건, CoverageProbe ok
기능 VERIFY-OK (심볼릭 링크 9개 · 번들 도구 3종 · LFS 필터 · copyutil 흐름)
배포 docker.io/paasup 에 push 후 운영 argocd 교체 — 파드 8종 Running,
admin 로그인, Application 2건 Synced/Healthy, repo-server 렌더링 확인
(서버가 go1.26.6 · helm v4.2.4 로 보고 — 우리 빌드가 맞다)
카탈로그 차단은 574 → 239 건이 됐고(7.7.0 삭제 · dex 비활성 · 이 커밋), 남은 239 건은
전부 동결된 7.8.11 몫이다. 운영에 쓰는 10.4.0 은 0 건이다. 경과·미결은 MEMORY.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,12 +5,82 @@
|
||||
- 프로젝트 개요·설계 원칙 → [CLAUDE.md](CLAUDE.md)
|
||||
- SBOM·CVE 게이트 메커니즘 → [doc/sbom-pipeline.md](doc/sbom-pipeline.md)
|
||||
|
||||
최종 갱신: 2026-08-12
|
||||
최종 갱신: 2026-08-19
|
||||
|
||||
---
|
||||
|
||||
## 다음 작업
|
||||
|
||||
**argo-cd@10.4.0 이 게이트 차단 0건으로 완결됐다(2026-08-19).** ArgoCD 가 k8s 1.35 를
|
||||
지원하지 못해 시작한 업그레이드가 CVE 조치까지 이어진 건이다.
|
||||
|
||||
**발단 — k8s 1.33 신규 필드로 ArgoCD 가 전멸했다.** `.status.terminatingReplicas`(k8s 1.33
|
||||
도입)를 ArgoCD v2.14.5(k8s 라이브러리 0.31/0.32)가 몰라서, `ServerSideApply=true` 인
|
||||
Application 이 전부 `ComparisonError: field not declared in schema` 로 죽었다. 차트
|
||||
7.8.11 → 10.4.0(ArgoCD v3.5.1, k8s 라이브러리 0.36.1)으로 해소했고 dev 클러스터
|
||||
(1.35.7+rke2r1)에서 발생·해소를 모두 실측했다.
|
||||
|
||||
**CVE 조치 — 레버를 순서대로 적용해 574 → 239건.**
|
||||
|
||||
| 조치 | 차단 |
|
||||
|---|---:|
|
||||
| 시작(7.7.0·7.8.11·10.4.0 전부) | 574 |
|
||||
| `argo-cd/7.7.0` 삭제(구버전, EOSL 이미지 2종 포함) | 337 |
|
||||
| `dex.enabled=false` — 쓰지도 않는데 떠 있었다 | 280 |
|
||||
| `images/argocd/` 자체 빌드 | **239** |
|
||||
|
||||
남은 239건은 전부 **동결된 7.8.11**(직전 버전) 몫이고, 운영에 쓰는 **10.4.0 은 0건**이다.
|
||||
7.8.11 을 언제 지울지가 미결이다 — 지우면 게이트가 PASS 로 떨어진다.
|
||||
|
||||
**dex 를 끈 것이 가장 값싼 레버였다.** ArgoCD 는 `oidc.config` 가 있으면 dex 를 우회하는데
|
||||
차트 기본값이 `enabled: true` 라 유휴 파드가 떠서 차단의 절반 이상을 만들고 있었다.
|
||||
dipup 도 항상 `configs.cm.oidc.config` 로 Keycloak 에 직접 붙으므로 dex 를 쓰지 않는다.
|
||||
**대응 레버를 보기 전에 "이 컴포넌트가 필요하긴 한가" 를 먼저 묻는다** 는 교훈이다.
|
||||
|
||||
**`images/argocd/` 는 기존 자체 빌드 중 가장 크다** — Go 프로젝트 4개(argocd·helm·
|
||||
kustomize·git-lfs) + C 2개(tini·connect-proxy) + Node UI. 업스트림이 번들 도구를 **미리
|
||||
빌드된 릴리스 바이너리로 내려받기** 때문에 그 바이너리의 Go 버전을 우리가 통제할 수 없고,
|
||||
같은 CVE 가 여러 바이너리에 공유돼 **부분 조치가 무효**였다(본체만 고치면 거의 줄지 않았다).
|
||||
|
||||
실측 함정 셋:
|
||||
|
||||
- **`cp --update=none` 이 베이스 OS 를 갈랐다.** 차트의 repo-server init 컨테이너가 쓰는
|
||||
이 형식은 GNU coreutils 9.3+ 에서만 된다. BCI 15.7 로 빌드한 이미지가 게이트도
|
||||
`verify.sh` 도 통과하고 **배포 시점에** `Init:CrashLoopBackOff` 로 죽었다. BCI 16.0
|
||||
(coreutils 9.6)으로 올려 해소했고, `verify.sh` 가 이 명령을 직접 실행해 다음엔 빌드
|
||||
단계에서 걸리게 했다. **게이트 PASS 는 동작을 증명하지 않는다** 의 실제 사례다.
|
||||
- **같은 날 재빌드는 태그가 겹친다.** 태그에 빌드일을 넣어도 같은 날 다시 빌드하면 태그가
|
||||
같아, 노드가 캐시한 옛 digest 가 그대로 쓰인다(`imagePullPolicy: IfNotPresent`).
|
||||
이번엔 검증용으로 `Always` 를 넣어 우회했다 — 프레임워크 차원의 미결 사항이다.
|
||||
- **모듈 업그레이드 제안값은 최소치다.** 모듈 간 제약으로 더 올려야 할 수 있다
|
||||
(`requires <module>@vX, not @vY` 로 빌드가 실패한다).
|
||||
|
||||
**Go 모듈 CVE 조치를 자동화했다** — `scripts/build/suggest-go-upgrades.py` 가 스캔
|
||||
리포트의 `FixedVersion` 에서 `GO_MODULE_UPGRADES` / `GO_BUILDER_TAG` 를 산출한다. 사람이
|
||||
CVE 를 훑어 최대값을 고르지 않는다. 다음 조치에서 바뀌는 것은 `build.env` 뿐이고
|
||||
Dockerfile 은 그대로다(모듈 목록·패키지 목록을 전부 값으로 뺐다).
|
||||
|
||||
### 남은 것 (argo-cd)
|
||||
|
||||
- `argo-cd/7.8.11` 삭제 여부 — 지우면 게이트 PASS
|
||||
- 자체 빌드 이미지의 정식 반영 경로: CI `build-image.yml`(`workflow_dispatch`) 로 다시 태워
|
||||
볼지, 로컬 push 로 끝낼지. 이번엔 로컬에서 `docker.io/paasup` 에 push 하고 운영 argocd 를
|
||||
교체해 동작 확인까지 마쳤다.
|
||||
|
||||
### 별건으로 발견한 airflow 문제 2건
|
||||
|
||||
- **매니페스트가 비결정적이다.** `webserver-secret-key-secret.yaml` 이 `randAlphaNum` 으로
|
||||
매 렌더마다 새 값을 만들어, repo-server 의 매니페스트 캐시가 날아갈 때마다(ArgoCD 재시작·
|
||||
hard refresh) webserver 가 롤링되고 웹 세션이 끊긴다. reconcile 만으로는 재렌더가 일어나지
|
||||
않아 상시 루프는 아니다(4시간 19분 동안 롤아웃 0회로 확인). `webserverSecretKey` 또는
|
||||
`webserverSecretKeySecretName` 을 지정하면 해소된다. 카탈로그 기본값에 없어 모든 테넌트가
|
||||
해당된다.
|
||||
- **migrations job 이 5분마다 재실행된다.** `migrateDatabaseJob.useHelmHooks: false` 로
|
||||
hook 이 아닌 추적 리소스가 됐는데 `ttlSecondsAfterFinished: 300` 이 그대로라, TTL 삭제 →
|
||||
ArgoCD selfHeal 재생성이 반복된다. `ttlSecondsAfterFinished` 를 끄거나 hook 으로 되돌린다.
|
||||
|
||||
---
|
||||
|
||||
**apisix@2.16.0 전체가 게이트 PASS 로 완결됐다(2026-08-12).** `images/adc/` 신규 추가로
|
||||
마지막 남은 이미지까지 해소 — 차단 55건(2026-08-12 초 기준) → **0건**. adc·
|
||||
apisix-ingress-controller·apisix(paasup/apisix) 세 자체 빌드 이미지 전부 게이트
|
||||
|
||||
Reference in New Issue
Block a user