컨테이너 이미지 성분 분석:소스 코드 스캔으로는 보이지 않는 층
익숙한 혼란
팀이 SCA를 CI에 붙였고 소스 스캔은 깨끗했습니다. 그런데 보안팀이 이미지 스캐너를 돌리자 심각도 높은 취약점이 수십 건 보고됩니다. 개발 쪽의 첫 반응은 대개 “스캐너가 이상하다”입니다.
스캐너는 이상하지 않습니다. 두 도구가 보고 있는 대상이 근본적으로 다를 뿐입니다.
이미지 안에는 무엇이 들어 있나
전형적인 애플리케이션 이미지에는 성격이 다른 세 층의 컴포넌트가 들어 있습니다.
1층: 베이스 이미지(OS 층) — Dockerfile 첫 줄 FROM 뒤에 쓴 것입니다. 운영체제 사용자 공간 전체를 끌고 옵니다. glibc, openssl, bash, coreutils, 패키지 매니저 자체까지. 이 층의 컴포넌트 수는 보통 애플리케이션 의존성보다 훨씬 많으며, 어떤 애플리케이션 선언 파일에도 나타나지 않습니다.
2층: 시스템 패키지 — 빌드 과정에서 apt-get install이나 apk add로 넣은 것들입니다. curl, git, 컴파일러 툴체인, 각종 런타임 라이브러리. 빌드를 위해 설치하고, 삭제를 잊고, 운영 이미지에 그대로 남는 경로를 자주 밟습니다.
3층: 언어 의존성 — package.json, pom.xml, requirements.txt에 선언된 것들입니다. 이 층, 그리고 오직 이 층만이 소스 수준 SCA가 보는 범위입니다.
그러니 “소스는 깨끗한데 이미지에 취약점이 있다”는 모순이 아닙니다. 문제는 내가 쓰지 않았지만 확실히 돌아가고 있는 두 층에 있습니다.
베이스 이미지에 리스크가 몰리는 이유
베이스 이미지의 문제는 품질이 낮아서가 아니라 세 가지 구조적 요인 때문입니다.
용량이 곧 공격면입니다. 완전한 ubuntu:22.04는 수백 개 패키지를 동반하고, 그 대부분은 애플리케이션이 한 번도 건드리지 않습니다. 그래도 CVE는 스캔에 나타나고, 설명이나 수정을 요구받습니다.
업데이트 지연. FROM node:18은 버전 고정처럼 보이지만 태그는 움직입니다. 그리고 팀이 이미지를 재빌드하는 주기는 몇 달에 한 번인 경우도 드물지 않습니다. 베이스 이미지의 보안 업데이트는 자동으로 운영 환경에 도달하지 않습니다.
상속으로 인한 비가시성. 내 이미지가 다른 팀의 중간 이미지를 FROM 하고, 그 이미지가 또 다른 것을 FROM 합니다. 최종 운영 이미지의 컴포넌트는 서너 층 위의 결정에서 비롯될 수 있고, 그 사슬 전체를 본 사람은 아무도 없습니다.
거버넌스 3원칙
1. 간결하고 유지보수가 약속된 베이스 이미지를 고릅니다.
distroless, 공식 slim 변형, alpine — 선정 기준은 “어느 것이 가장 작은가”가 아니라 “실행 요건을 충족하면서 컴포넌트가 가장 적고, 보안 업데이트 주기가 명시된 것은 무엇인가”입니다. 패키지 하나가 줄면 잠재적 CVE 하나와 불필요한 설명 한 번이 줄어듭니다.
2. 이미지 스캔을 CI에, 소스 스캔과 나란히 둡니다.
둘은 대체가 아니라 보완입니다. 소스 SCA는 의존성 도입 시점에서 막고(더 이르고 수정 비용이 낮음), 이미지 스캔은 산출물 층에서 받아냅니다(더 넓고 선언하지 않은 부분까지 덮음). 이상적인 게이트 위치는 이미지 빌드 후, 레지스트리 푸시 전입니다. 이 시점이면 아직 재빌드할 수 있지만, 푸시 후에 발견하면 롤백이 됩니다.
3. 베이스 이미지 버전을 전사 차원에서 일원 관리합니다.
팀마다 FROM을 따로 고르는 대신, 회사가 보안 심사를 거친 기준 이미지 묶음을 유지하고 각 팀이 거기서 파생합니다. 효과는 취약점 발생 시 가장 뚜렷합니다. 기준을 한 번 고치면 하위는 재빌드만 하면 되고, 20개 팀이 각자 조사하는 사태를 피할 수 있습니다.
SBOM은 어느 층까지 덮어야 하나
컨테이너화된 제품의 SBOM을 고객이나 규제 기관에 제출한다면, 언어 의존성만 내보내는 것으로는 부족합니다. 받는 쪽이 기대하는 것은 “이 이미지에 무엇이 들어 있는가”이지 “이 애플리케이션이 무엇을 선언했는가”가 아닙니다.
완전한 컨테이너 SBOM에는 베이스 이미지 식별자와 다이제스트, 시스템 패키지 목록(버전 포함), 언어 의존성 트리, 각 층의 출처 정보가 포함되어야 합니다. 다행히 이것은 자동화할 수 있습니다. CI에서 빌드된 이미지에 성분 분석을 수행하고 SPDX 또는 CycloneDX로 내보내 이미지와 함께 배포하면 됩니다.
주의할 점은 층의 추적 가능성입니다. 같은 컴포넌트가 여러 층에 나타날 수 있고 출처도 수정 경로도 다릅니다. 베이스 이미지의 openssl은 베이스 이미지 교체로 고치고, 애플리케이션 의존성의 것은 선언 변경으로 고칩니다. SBOM이 출처를 구분하지 않으면 받은 쪽은 누구에게 수정을 요청해야 할지 판단할 수 없습니다.
“스캔 통과”에서 “성분을 안다”로
컨테이너화는 산출물을 “jar 하나”에서 “실행 환경 전체”로 바꾸며 소프트웨어 공급망의 경계를 두 층 아래로 확장했습니다. 성분 투명성 요구도 그에 맞춰 아래로 내려가야 합니다.
강조하고 싶은 것은 이것이 보안만의 문제가 아니라 라이선스 문제이기도 하다는 점입니다. 베이스 이미지에 포함된 GPL 컴포넌트와 시스템 패키지의 라이선스 의무도 이미지 배포와 함께 전파됩니다. 애플리케이션 의존성만 보는 컴플라이언스 감사는 컨테이너화된 배포 앞에서 불완전합니다.
---
관련 글: SBOM 필드 실무 가이드 · CI 파이프라인에서 SCA 게이트 설정 · CleanSource SCA의 컨테이너 이미지 분석
