보안 서비스를 위한 'Chainguard' 이미지
Source: Dev.to
If you work in DevOps or system‑backend development, one of the biggest sources of stress is security. Even though it’s always at the top of the priority list, its substance often feels elusive.
Assume you’re maintaining a Kubernetes cluster or building Docker images (most DevOps engineers deal with at least one of these). Even if your own code is secure, the base image — Debian, Ubuntu, Alpine, etc. — often carries technical debt, and that debt can contain security risks that compromise your service. This is where Chainguard images come in.
Chainguard란?
간단히 말해, Chainguard Images는 컨테이너 전용으로 설계된 Linux “undistro”인 Wolfi 위에 구축된 “secure‑by‑default” 컨테이너 이미지입니다.
표준 Docker Hub 이미지와 달리 Chainguard 이미지에는 몇 가지 뚜렷한 특징이 있습니다:
- Distroless – 애플리케이션을 실행하는 데 필요한 최소한만 포함합니다. 셸도 없고, 런타임에 패키지 매니저도 없으며, 불필요한 부피도 없습니다.
- Daily Rebuilds – 모든 이미지는 상위 소스에서 매일 재빌드되어 취약점이 즉시 패치됩니다.
- SBOMs & Signing – 소프트웨어 구성 목록(SBOM)과 Sigstore 서명이 기본 제공됩니다.
대결: Official vs. Chainguard
가장 즉각적인 이점은 잡음이 감소한다는 점입니다. 아래는 Trivy를 사용해 표준 Python 이미지와 Chainguard 버전을 비교한 결과입니다.
| 이미지 | 취약점 |
|---|---|
Official (python:3.11) | > 300개의 취약점, 여기에는 “Critical”(심각) 및 “High”(고위험) 등급이 포함됩니다 |
Chainguard (cgr.dev/chainguard/python:latest) | 0 CVE |
이것은 마법이 아니라 공격적인 최소주의입니다. 애플리케이션이 사용하지 않는 OS 구성 요소를 제거함으로써 공격 표면을 크게 줄일 수 있습니다.
실습: 마이그레이션 가이드
마이그레이션은 특히 Chainguard의 distroless 이미지에서는 단순한 교체가 아닙니다. 런타임에 쉘과 패키지 관리자가 없기 때문에 멀티‑스테이지 빌드를 사용해야 합니다.
도구 제한
일반적인 (그리고 약간 취약한) Dockerfile은 다음과 같이 보일 수 있습니다:
# Standard Python Image
FROM python:3.9-slim
WORKDIR /app
# Installing dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Running as root (default)
CMD ["python", "app.py"]
Chainguard를 사용한 마이그레이션 버전
# 1️⃣ Builder stage
# Use the `-dev` tag because we need a shell and build tools (gcc, headers, etc.)
FROM cgr.dev/chainguard/python:latest-dev AS builder
WORKDIR /app
# Create a virtual environment
RUN python -m venv /app/venv
ENV PATH="/app/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 2️⃣ Runtime stage
# This image has no shell or package manager; it is pure runtime.
FROM cgr.dev/chainguard/python:latest
WORKDIR /app
# Copy the virtual environment from the builder
COPY --from=builder /app/venv /app/venv
COPY . .
# Use the virtualenv
ENV PATH="/app/venv/bin:$PATH"
# Chainguard runs as a non‑root user (`nonroot`) by default.
# No need to create a user manually.
CMD ["python", "app.py"]
주의사항 & 문제 해결
| 이슈 | 설명 | 해결 방법 |
|---|---|---|
| “파드에 exec 할 수 없어요!” | 런타임 이미지에 쉘( /bin/sh 또는 bash)이 없습니다. | 임시 디버그 컨테이너를 사용하거나 디버깅을 위해 -dev 태그로 전환합니다. |
| 권한 | Chainguard는 비루트 실행을 강제합니다. /에 쓰거나 런타임에 패키지를 설치하려 하면 실패합니다. | 애플리케이션이 쓰기 가능한 위치(예: /tmp 또는 마운트된 볼륨)만 사용하도록 보장합니다. |
sudo 없음 | sudo가 포함되지 않았으며, 런타임에 패키지 관리자를 사용할 수 없습니다. | 빌더 단계에서 필요한 특권 작업을 수행하고(USER root가 필요하면 사용), 최종 이미지에서는 비루트 사용자로 실행하도록 설계합니다. |
Chainguard 이미지는 최소 권한 원칙을 따르기 위해 sudo(및 대부분의 패키지 관리자)를 제공하지 않습니다. 기본적으로 사전 구성된 비루트 사용자(UID 65532)로 실행되어 권한 상승 공격을 근본적으로 차단합니다.
해결책: 특권 작업은 빌드 단계로 옮깁니다. 가능한 경우 애플리케이션을 루트 없이 실행하도록 리팩터링하십시오. 시스템 종속성을 설치해야 하는 경우(예: 시스템 패키지 설치) 멀티‑스테이지 빌드에서 USER root 지시어를 사용해 수행한 뒤, 최종 이미지에서는 다시 비루트 사용자로 전환합니다.
“Latest‑Only” 패키지 철학
Chainguard의 기반 패키지 생태계인 Wolfi는 Rolling Release 모델을 따르며, 이는 패키지 버전 관리와 관련된 특정 제약을 도입합니다.
- 고정 레포 제약 – Chainguard 이미지는 신뢰할 수 있는 Wolfi 또는 Chainguard 레포지토리에서만 가져옵니다. 서드‑파티 또는 검증되지 않은 레포를 추가하는 것은 “Zero CVE” 보장을 유지하기 위해 강력히 권장되지 않습니다.
- Latest‑Only 규칙 – 공개 Wolfi 레포지토리는 일반적으로 최신 안정 버전만 유지합니다. 오래된 버전을 고정하려고 하면(
apk add openssl=1.1.1등) 해당 버전이 제거되어 “Unsatisfiable constraints” 오류가 발생합니다.
철학: Chainguard는 최신 검증된 패키지만 사용하도록 강제함으로써 오래된 취약점이 남아 있는 위험을 크게 줄입니다.
요약
Chainguard 이미지는 컨테이너화된 워크로드를 위한 안전하고, 최소하며, 재현 가능한 기반을 제공합니다. 그 대가로 워크플로우가 바뀝니다: 다단계 빌드를 채택하고, 비루트 실행을 수용하며, 최신 검증된 패키지에 의존해야 합니다. 일단 적응하면 취약점 표면이 크게 줄어들고 보안 스캔 도구에서 발생하는 잡음도 현저히 감소합니다.
“최신‑전용” 접근 방식은 이미지에 알려진 취약점이 무심코 포함되는 것을 방지합니다. 시스템에 레거시·EOL(End‑of‑Life) 버전이 절대적으로 필요하다면, 오래된 패치 버전을 위한 전용 저장소를 제공하는 엔터프라이즈 티어를 고려해야 할 수도 있습니다.
“레거시 버전” 유지 관리의 함정
팀이 프로덕션 수준 서비스에서 오래된 Docker 이미지(예: node:14 또는 python:3.6)를 고수하는 가장 큰 이유 중 하나는 안정성이다.
“동작하니 건드리지 말아 주세요.”
표준 Docker 환경에서 python:3.6‑slim과 같이 버전을 고정하면 기반 OS(대개 오래된 Debian 릴리스)가 보안 업데이트를 받지 않게 된다. 즉, OS 수준 취약점이라는 시한폭탄 위에 앉아 있는 것이다.
모두가 조치를 취해야 한다는 것을 알지만, 마이그레이션은 단순히 코드를 바꾸는 것만으로는 해결되지 않는다. 방대한 테스트와 수많은 사용 사례를 고려해야 한다. Chainguard는 이 패러다임을 바꾼다: 특정 언어 버전을 사용하더라도 Chainguard는 기반 Wolfi OS 레이어를 매일 재빌드한다.
- 장점: 언어 버전의 안정성을 최신 OS 보안과 함께 얻을 수 있다.
- 단점: 이미지 해시가 매일 변경된다. 몇 달 동안 정확한 SHA 다이제스트에 의존해 온 파이프라인은 깨지며, 롤링 태그를 받아들여야 한다.
- 비용: 지원 종료된 언어 버전(예: Python 3.7 또는 Java 8)의 경우 Chainguard는 보통 해당 이미지를 유료 티어로 이동시킨다. 무료 티어는 현재 지원되는 버전에 집중한다.