a168a3c7e6
scan-sbom.sh 에 커버리지 자가진단이 없는데도 체크리스트가 cov= 확인을 지시해 같은 문서 104-106줄과 모순됐다. 실제 동작(findings 0건 시 보수적 실패)과 판단 방법으로 교체. Dockerfile 헤더 주석의 doc/scripts/ 경로도 scripts/pipeline/ 로 갱신(경로 이전 시 누락됨). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
110 lines
7.7 KiB
Markdown
110 lines
7.7 KiB
Markdown
# 자체 빌드 이미지 작업 규칙
|
|
|
|
[CLAUDE.md](../CLAUDE.md) 에서 분리했다. 새 자체 빌드 이미지를 추가하거나(CVE 게이트
|
|
대응 우선순위 중 "자체 빌드") 기존 이미지의 빌드 정의를 바꿀 때만 참고한다.
|
|
|
|
security-catalog 레포에서 검증한 자체 빌드 프레임워크를 포팅했다. **이 시점에는 아직
|
|
이 레포에 도입된 자체 빌드 이미지가 없다** — `images/` 디렉토리 자체가 없다. 아래는
|
|
프레임워크가 어떻게 동작하는지와, 첫 이미지를 추가할 때 지켜야 할 규칙이다.
|
|
|
|
## 원칙 1 — 오케스트레이션은 항상 하나, 이미지 종류는 몰라도 된다
|
|
|
|
**`scripts/build/build-hardened-image.sh` 하나가 모든 자체 빌드 이미지를 빌드한다.**
|
|
이미지가 OS 패키지를 재설치하는 것이든, 소스를 직접 컴파일하는 것이든 스크립트는
|
|
같다 — 차이는 전부 `images/<image>/` 안에 있다.
|
|
|
|
**새 오케스트레이션 스크립트를 만드는 것은 최후의 수단이다.** "이 이미지는 성격이
|
|
다르다"는 이유만으로 새 스크립트를 만들지 않는다. 절차(빌드 → 기능검증 → SBOM → 스캔
|
|
→ 게이트 → push)는 이미지 종류와 무관하게 동일하고, 차이는 Dockerfile 내부(무엇을
|
|
어떻게 설치·컴파일하는가)에만 있어야 한다.
|
|
|
|
### `build-hardened-image.sh` 가 요구하는 계약
|
|
|
|
`images/<image>/<variant>.build.env` 가 다음을 선언하면 스크립트는 이미지 종류를
|
|
몰라도 된다:
|
|
|
|
| 키 | 의미 |
|
|
| --- | --- |
|
|
| `DOCKERFILE` | `images/<image>/` 기준 상대 경로 |
|
|
| `TARGET` | `docker build --target` 에 넘길 스테이지명 |
|
|
| `BUILD_ARGS` | 공백 구분 변수명 목록. 여기 나열한 것만 `--build-arg` 로 전달된다 |
|
|
| `APP_VERSION` | 태그 프리픽스·`verify.sh` 전달용 범용 버전 문자열 (유일한 필수값) |
|
|
|
|
그 외 이미지별 변수(예: `PG_MAJOR`, `SOURCE_COMMIT`)는 build.env 에 적기만 하면 **자동으로
|
|
`verify.sh` 의 환경변수로 전달된다** — 스크립트가 무엇을 넘겨야 하는지 알 필요가 없다.
|
|
|
|
### `verify.sh` 는 호스트에서 bash 로 실행된다
|
|
|
|
게스트 컨테이너에 stdin 으로 셸 스크립트를 주입하는 방식이 아니다 —
|
|
`env TAG=... PLATFORM=... <build.env 변수들> bash images/<image>/verify.sh` 로 호출된다.
|
|
**이유**: 이미지에 셸이 없을 수 있다(distroless 계열 최종 이미지는 `/bin/sh` 가 없다).
|
|
호스트 스크립트는 셸이 있는 이미지엔 `docker run --entrypoint sh ... <<'EOF'` 로 게스트
|
|
스크립트를 쓸 수 있고, 셸이 없는 이미지엔 `docker run --entrypoint <바이너리>` 로 직접
|
|
실행할 수 있다 — 호스트 실행이 상위 호환이다. 마지막 줄에 `VERIFY-OK` 를 출력해야
|
|
통과로 판정된다.
|
|
|
|
## 원칙 2 — 최종 런타임 베이스 OS 는 아직 미결
|
|
|
|
security-catalog 는 자체 빌드 이미지의 최종 런타임 베이스로 SUSE BCI 하나만 쓰기로
|
|
결정했지만, 그 결정은 이 레포에 이식하지 않았다. **dip-catalog 는 베이스 OS 정책이
|
|
아직 없다** — 처음 자체 빌드 이미지를 추가할 때 정하고, 결정 배경을 남긴다(카탈로그
|
|
전용 `doc/decisions/` 관례는 아직 없으므로 우선 해당 PR 설명과 `MEMORY.md`에 기록).
|
|
|
|
- "업스트림과 최대한 동일하게" 라는 기본 원칙과 특정 베이스 OS 채택이 충돌할 수 있다
|
|
(예: 정적 링크 바이너리에 어떤 최소 이미지를 쓸지). 그 경우 무엇을 우선했는지와 왜인지
|
|
기록한다.
|
|
- 최소 이미지(distroless 류, BCI micro 류 등)는 `sed`/`grep` 같은 흔한 도구가 없을 수
|
|
있다 — `verify.sh` 게스트 스크립트는 그런 도구에 의존하지 말고 순수 셸 루프
|
|
(`while IFS= read -r line; do ...; done`)로 작성하는 편이 안전하다.
|
|
- 빌더 스테이지(컴파일용, 최종 이미지에 남지 않는 스테이지)는 이 정책 대상이 아니다 —
|
|
공식 언어 이미지(`golang` 등)를 그대로 써도 된다. 정책이 적용되는 것은 **스캔·배포
|
|
대상인 최종 스테이지**뿐이다.
|
|
|
|
## 두 가지 유형 (둘 다 같은 스크립트를 쓴다)
|
|
|
|
| 유형 | Dockerfile 이 하는 일 | 셸 유무 |
|
|
| --- | --- | --- |
|
|
| OS 패키지 재설치형 | 업스트림이 배포하는 산출물을 다른 배포판(zypper/apt 등)에 재설치 | 보통 있음 — 게스트 스크립트로 검증 |
|
|
| 소스 컴파일형 | 업스트림 pinned commit 을 `go build` 등으로 직접 컴파일 | 최종 베이스에 따라 다름 |
|
|
|
|
어느 유형이든 이 셋만 새로 쓰면 된다: `<variant>.Dockerfile`, `<variant>.build.env`,
|
|
`verify.sh`(+ `README.md`).
|
|
|
|
## 신규 이미지 추가 체크리스트
|
|
|
|
1. **상위 태그 교체 → 베이스 OS 교체 순으로 먼저 검토했는가.** 그것으로 해소되면
|
|
자체 빌드로 가지 않는다. 특히 CVE 가 OS 패키지가 아니라 애플리케이션/바이너리 자체에
|
|
정적으로 포함된 것이면(예: Go 모듈, 정적 링크된 라이브러리) 베이스 OS 교체는
|
|
원천적으로 통하지 않는다 — 이 판단 근거를 남긴다(PR 설명 또는 `MEMORY.md`)
|
|
2. **어느 유형인지 판단한다** ("업스트림 산출물을 다른 배포판에 재설치" vs "소스를 직접
|
|
컴파일"). 최종 베이스 OS 를 이때 정한다(원칙 2)
|
|
3. `images/<image>/<variant>.Dockerfile`·`<variant>.build.env`·`verify.sh`·`README.md`
|
|
작성. 업스트림 Dockerfile 과의 대응 관계·차이를 파일 상단 주석으로 남긴다.
|
|
**`FROM` 에 쓰는 `ARG` 는 반드시 파일의 첫 `FROM` 이전(전역 스코프)에 선언한다** —
|
|
스테이지 내부(어떤 `FROM` 뒤)에 선언하면 그 스테이지 지역 변수가 되어 이후 `FROM` 의
|
|
이미지명 해석에 쓰이지 않고 빈 이미지명 에러가 난다
|
|
4. 로컬 빌드:
|
|
```sh
|
|
IMAGE=<image> BASE_OS=<variant> bash scripts/build/build-hardened-image.sh /tmp/out
|
|
```
|
|
`cve-gate.md` 로 실효 C/H 0 확인. **dip-catalog 의 `scan-sbom.sh` 는 커버리지
|
|
자가진단(`CoverageProbe`)이 없다** — 패키지가 적은 이미지(최소 베이스 등)는 findings
|
|
전 심각도 0건이라는 이유만으로 게이트가 "데이터 커버리지 이상"으로 실패할 수 있다.
|
|
진짜 0건인지 스캐너 데이터 부재인지는 사람이 `trivy-reports/<tag>.json`(OS/패키지 수)을
|
|
보고 직접 판단한다(`doc/sbom-pipeline.md` 참고)
|
|
5. **게이트 PASS 는 "동작한다" 를 증명하지 않는다.** CVE 스캐너는 CVE 와 무관한 런타임
|
|
요구사항(예: 오퍼레이터가 자신의 파일 레이아웃에 의존하는 것)을 전혀 보지 못한다.
|
|
실제 배포 검증을 반드시 한다 — 자동화된 배포 테스트 절차는 아직 없으므로 해당 차트를
|
|
dev 클러스터에 배포해 수동으로 기능을 확인한다. 업스트림과 다르게 만든 부분은 전부
|
|
이유를 확인하고 남긴다.
|
|
6. 카탈로그 values(`custom-values.yaml`/`dip-values.yaml` 등) 갱신, 조사·결정 근거를
|
|
PR 설명과 `MEMORY.md`에 기록. `images/**`+`manifests/helm/**` 는 PR 로.
|
|
7. CI 자동화: `build-image.yml` 은 이미 `image` 입력으로 파라미터화돼 있다 —
|
|
`images/<image>/catalog.env` 만 추가하면 별도 워크플로 수정 없이 태울 수 있다.
|
|
|
|
## SBOM·스캔·게이트는 절대 다시 만들지 않는다
|
|
|
|
`scripts/pipeline/scan-sbom.sh` · `scripts/pipeline/cve-gate.py` 는 이미지 종류와 무관하게
|
|
동작한다(단, dip-catalog 의 `scan-sbom.sh` 는 커버리지 자가진단이 없다 — `doc/sbom-pipeline.md`
|
|
참고). `build-hardened-image.sh` 가 이미 이 둘을 호출한다 — 이미지별로 다시 구현하지 않는다.
|