33e91c0536
업스트림 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>
105 lines
5.2 KiB
Markdown
105 lines
5.2 KiB
Markdown
# argocd (자체 빌드)
|
|
|
|
`quay.io/argoproj/argocd` 를 대체하는 하드닝 이미지. 업스트림 소스를 pinned commit 으로
|
|
직접 컴파일하고, 업스트림이 릴리스 바이너리로 내려받던 번들 도구(helm·kustomize·git-lfs)까지
|
|
같은 Go 툴체인으로 다시 만든다.
|
|
|
|
```sh
|
|
IMAGE=argocd BASE_OS=source bash scripts/build/build-hardened-image.sh /tmp/out
|
|
```
|
|
|
|
## 왜 자체 빌드인가
|
|
|
|
이 이미지의 차단 CVE 는 대부분 OS 패키지가 아니라 **바이너리에 정적 링크된 Go 모듈**이다.
|
|
그래서 앞선 두 레버가 모두 통하지 않는다.
|
|
|
|
- **상위 태그 교체 불가** — 이미 최신 릴리스다. 번들 도구도 각각 최신이고, 낡은 Go 는
|
|
각 업스트림 프로젝트의 선택이라 버전을 올려도 바뀌지 않는다.
|
|
- **베이스 OS 교체로는 거의 안 잡힌다** — 모듈은 컴파일된 바이너리 안에 있다.
|
|
|
|
## 왜 번들 도구까지 다시 만드는가
|
|
|
|
업스트림 Dockerfile 은 helm/kustomize/git-lfs 를 `hack/install.sh` 로 **미리 빌드된 릴리스
|
|
바이너리를 내려받아** 넣는다. 그 바이너리의 Go 버전은 우리가 통제할 수 없다.
|
|
|
|
그리고 **부분 조치는 효과가 없다.** 같은 `stdlib`·`x/crypto` 취약점이 다섯 바이너리에 각각
|
|
들어있어서, 한 바이너리만 고치면 나머지에 그대로 남는다. 고유 CVE 는 소수이고 대부분이
|
|
공유분이라 전부 다시 만들어야 게이트가 0 이 된다(실측 확인 — argocd 본체만 조치했을 때는
|
|
거의 줄지 않았다).
|
|
|
|
`pebble` 은 우리가 넣은 것이 아니라 ubuntu 베이스에 딸려온 것이다 — 베이스를 SUSE BCI 로
|
|
바꾸면 함께 사라진다.
|
|
|
|
## 다음 CVE 조치 — Dockerfile 은 건드리지 않는다
|
|
|
|
버전도 모듈 목록도 Dockerfile 에 박혀 있지 않다. 새 차단 CVE 가 나오면 **`source.build.env`
|
|
한 곳만** 바뀐다.
|
|
|
|
```sh
|
|
# 1) 게이트 리포트에서 업그레이드 값을 산출한다 (손으로 찾지 않는다)
|
|
python3 scripts/build/suggest-go-upgrades.py --reports sbom-out/trivy-reports --image argocd
|
|
|
|
# 2) 출력된 GO_MODULE_UPGRADES / GO_BUILDER_TAG 를 source.build.env 에 반영
|
|
|
|
# 3) 재빌드 — 게이트까지 한 번에 돈다
|
|
IMAGE=argocd BASE_OS=source bash scripts/build/build-hardened-image.sh /tmp/out
|
|
```
|
|
|
|
| 새 CVE 유형 | 바꾸는 값 |
|
|
|---|---|
|
|
| Go `stdlib` | `GO_BUILDER_TAG` |
|
|
| Go 모듈 | `GO_MODULE_UPGRADES` 에 `<module>@<version>` 추가 |
|
|
| OS 패키지 | `RUNTIME_BASE` 또는 `RUNTIME_PACKAGES` |
|
|
| 구성요소 새 릴리스 | 해당 `*_VERSION` |
|
|
|
|
`GO_MODULE_UPGRADES` 는 argocd·helm·kustomize·git-lfs **네 프로젝트에 공통 적용**된다.
|
|
[go-mod-upgrade.sh](go-mod-upgrade.sh) 가 각 프로젝트의 의존성 그래프에 있는 것만 골라
|
|
적용하므로 목록 하나를 그대로 재사용한다 — `go get` 은 의존성에 없는 모듈도 `go.mod` 에
|
|
추가해버리기 때문에 필요한 필터다.
|
|
|
|
> 제안값은 CVE 요건의 **최소치**다. 모듈 간 제약으로 더 올려야 할 수 있다 — 빌드가
|
|
> `requires <module>@vX, not @vY` 로 실패하면 그 버전으로 올린다.
|
|
|
|
## 업스트림과 다르게 한 부분
|
|
|
|
| 항목 | 업스트림 | 이 이미지 | 이유 |
|
|
|---|---|---|---|
|
|
| 최종 베이스 | `ubuntu` | `registry.suse.com/bci/bci-base` | 카탈로그는 SUSE BCI 하나만 쓴다(`.claude/image-authoring.md` 원칙 2). `pebble` 도 함께 소멸 |
|
|
| helm / kustomize / git-lfs | 릴리스 바이너리 다운로드 | 소스 컴파일 | 다운로드 바이너리의 Go 를 통제할 수 없다 |
|
|
| helm 버전 | 업스트림 pin | 최신 | kustomize·git-lfs 는 pin 이 이미 최신이라 그대로 |
|
|
| `tini` · `connect-proxy` | apt 패키지 | 소스 빌드 | SLE_BCI 에 둘 다 없다. 기능을 빼지 않기 위해 빌드 |
|
|
| Go 툴체인 | 업스트림 pin | `GO_BUILDER_TAG` | stdlib CVE 해소 |
|
|
| 취약 모듈 | 그대로 | `GO_MODULE_UPGRADES` 로 강제 업그레이드 | |
|
|
| `BUILD_DATE` | 빌드 시각 | 고정값 | 빌드 재현성 |
|
|
| UI | node 빌드 | **동일** | Go embed 로 바이너리에 들어가 생략 불가 |
|
|
|
|
애플리케이션 코드 자체는 pinned 태그 그대로다 — 최소 diff 원칙.
|
|
|
|
## SLE 패키지 매핑
|
|
|
|
업스트림 apt 목록을 SLE_BCI 이름으로 옮겼다(`.claude/image-authoring.md` 참고).
|
|
목록 자체는 `source.build.env` 의 `RUNTIME_PACKAGES` 에 있다.
|
|
|
|
| ubuntu | SLE_BCI |
|
|
|---|---|
|
|
| `git` | `git-core` |
|
|
| `tzdata` | `timezone` |
|
|
| `gpg` · `gpg-agent` | `gpg2` |
|
|
| `openssh-client` | `openssh-clients` |
|
|
| `ca-certificates` | `ca-certificates` (동일) |
|
|
| `tini` | **없음** → 소스 빌드 |
|
|
| `connect-proxy` | **없음** → 소스 빌드 |
|
|
|
|
## 검증
|
|
|
|
`verify.sh` 가 보는 것 — argocd 버전 문자열, 업스트림 심볼릭 링크 9개, 번들 도구 3개의
|
|
버전, tini·connect-proxy 실행 가능, git/gpg/ssh 존재, `/etc/gitconfig` 의 LFS 필터,
|
|
`/app/config` 디렉토리 구조와 래퍼 스크립트.
|
|
|
|
**게이트 PASS 는 "동작한다" 를 증명하지 않는다.** 실제 기동은 Kubernetes API 가 필요해
|
|
스모크 범위 밖이다 — dev 클러스터 배포 검증을 반드시 한다.
|
|
|
|
```sh
|
|
VALUES_FILE=<values> bash scripts/deploy-test/deploy-test-argo-cd.sh /tmp/deploy-test-argocd
|
|
```
|