컨테이너 이미지 하드닝(hardening)은 컨테이너 안에 꼭 필요한 것만 남겨 공격자가 파고들 틈을 줄이는 작업이다. 핵심 아이디어는 단순하다. 안에 든 게 적을수록 뚫을 것도 적다. 쓸데없는 셸, 패키지 매니저, 디버깅 도구가 이미지에 들어 있으면, 그건 공격자에게 그대로 넘어가는 연장통이 된다.
많은 팀이 편하다는 이유로 우분투 같은 풀사이즈 리눅스를 베이스 이미지로 쓴다. 앱 하나 돌리는 데 운영체제 한 벌이 통째로 딸려 온다. 그 안에는 내 앱이 절대 쓰지 않는 수백 개의 프로그램과 라이브러리가 잠들어 있고, 각각이 취약점을 품을 수 있다. 나는 이걸 "작은 짐을 옮기려고 대형 트럭을 통째로 끌고 다니는 격"이라고 표현한다.
이미지 안에 셸이 없으면, 뚫려도 할 수 있는 게 없다.
공격의 다음 단계는 대개 셸을 열고 도구를 내려받는 것이다. distroless와 rootless는 그 다음 단계 자체를 없애 공격을 그 자리에 묶어둔다.
distroless 앱과 런타임만 셸 없음 |
rootless root 아닌 사용자로 실행 |
읽기전용 파일시스템 수정 차단 |
최소화 공격면= 이미지 크기 |
공격면은 이미지 크기와 함께 커진다
컨테이너를 뚫는 전형적인 흐름을 떠올려보자. 공격자는 앱의 취약점을 이용해 컨테이너 안으로 침투한다. 그다음 하는 일은 대개 셸을 열고, 정찰 도구를 내려받고, 권한을 높이고, 다른 곳으로 이동하는 것이다. 그런데 이 모든 단계는 컨테이너 안에 그럴 만한 도구가 있어야 가능하다. 셸이 있어야 명령을 내리고, 패키지 매니저나 다운로드 도구가 있어야 추가 무기를 들여온다.
바로 여기가 하드닝이 노리는 지점이다. 이미지에서 셸, 패키지 매니저, 컴파일러, 네트워크 도구를 다 빼버리면, 침투에 성공한 공격자가 갑자기 텅 빈 방에 갇힌 꼴이 된다. 명령을 내릴 셸도, 도구를 받을 수단도 없다. 최초 침투를 완벽히 막을 수는 없어도, 그 다음으로 나아가는 길을 끊는 것이다. 방어의 무게중심이 "못 들어오게"에서 "들어와도 못 움직이게"로 옮겨간다.
distroless — 앱과 런타임만 남기기
distroless 이미지는 이름 그대로 리눅스 배포판의 군더더기를 걷어낸 이미지다. 셸도 없고, 패키지 매니저도 없고, 앱 실행에 딱 필요한 런타임과 라이브러리만 남는다. 자바 앱이면 자바 런타임과 내 애플리케이션만, 그 이상은 없다. 이미지 용량이 크게 줄어드는 건 덤이고, 진짜 이득은 취약점 스캔 결과가 극적으로 깨끗해진다는 점이다. 애초에 들어 있는 소프트웨어가 적으니 취약점이 붙을 자리도 적다.
멀티스테이지 빌드가 열쇠
distroless로 가는 실전 기법은 멀티스테이지 빌드다. 빌드 단계에서는 컴파일러와 도구가 잔뜩 든 무거운 이미지를 쓰되, 최종 결과물 단계에서는 완성된 실행 파일만 distroless 이미지로 복사해 넣는다. 요리 재료와 조리도구는 주방에 두고, 완성된 접시만 손님에게 내가는 것과 같다. 빌드에 필요한 온갖 도구가 최종 이미지에는 한 톨도 남지 않는다.
rootless — 최고 권한을 애초에 주지 않기
또 하나의 흔한 함정은 컨테이너 안 프로세스를 root(최고 관리자)로 돌리는 것이다. 별도 설정을 안 하면 많은 컨테이너가 기본적으로 root로 실행된다. 문제는 컨테이너 안의 root 권한이 특정 취약점과 만나면 호스트 시스템으로 탈출하는 발판이 될 수 있다는 점이다.
해법은 명확하다. 앱을 일반 사용자 권한으로 돌리는 것이다. 대부분의 애플리케이션은 관리자 권한이 필요 없다. 그저 자기 파일을 읽고 포트를 여는 정도면 충분하다. 이미지를 만들 때 전용 비관리자 사용자를 만들고 그 사용자로 앱을 실행하게 지정하면, 설령 컨테이너가 뚫려도 공격자가 손에 쥐는 권한이 처음부터 낮다.
| 항목 | 하드닝된 이미지 | 일반 풀사이즈 이미지 |
|---|---|---|
| 셸 포함 | 없음 | 있음 |
| 실행 사용자 | 비관리자 | 흔히 root |
| 포함 패키지 | 런타임 최소 | 운영체제 한 벌 |
| 침투 후 이동 | 도구 없어 어려움 | 연장통 그대로 |
더 얹을 수 있는 방어
- 읽기 전용 파일시스템 — 컨테이너의 파일시스템을 수정 불가로 잠그면, 공격자가 악성 파일을 심거나 앱을 변조하기 어려워진다.
- 권한 능력 최소화 — 리눅스 커널 권한(capability)을 필요한 것만 남기고 전부 떨궈, 프로세스가 할 수 있는 위험한 동작을 제한한다.
- 이미지 스캔 자동화 — 빌드 파이프라인에 취약점 스캐너를 넣어, 알려진 취약점이 있는 이미지가 배포로 넘어가지 못하게 막는다.
- 이미지 서명 검증 — 신뢰된 곳에서 서명된 이미지만 실행하도록 강제해, 바꿔치기된 이미지가 돌지 않게 한다.
디버깅이 어려워진다는 걱정이 있는데, 셸 없는 이미지도 요즘은 임시 디버그 컨테이너를 잠깐 붙여 살펴보는 방법이 있어 실무에서 큰 걸림돌은 아니다. 나는 하드닝을 "불편을 조금 감수하고 공격자에게 훨씬 큰 불편을 안기는 거래"라고 본다. 그 거래는 대체로 남는 장사다.
자주 묻는 질문
distroless 이미지는 디버깅이 불가능한가요?
불가능하지 않습니다. 이미지 자체에 셸이 없을 뿐, 필요할 때 임시 디버그 컨테이너를 같은 공간에 붙여 도구를 들고 들여다볼 수 있습니다. 평소에는 깨끗하고 안전하게 두되, 문제가 생겼을 때만 잠시 진단 도구를 붙이는 방식입니다.
rootless로 돌리면 뭐가 좋아지나요?
컨테이너가 뚫렸을 때 공격자가 얻는 권한이 처음부터 낮아집니다. 컨테이너 안의 root 권한은 특정 취약점과 결합하면 호스트로 탈출하는 발판이 될 수 있는데, 비관리자로 실행하면 그 위험이 크게 줄어듭니다. 대부분의 앱은 관리자 권한이 필요 없습니다.
이미지를 작게 만들면 정말 더 안전한가요?
일반적으로 그렇습니다. 안에 든 소프트웨어가 적을수록 취약점이 붙을 자리와 공격자가 악용할 도구가 줄어듭니다. 작은 이미지는 취약점 스캔 결과가 깨끗하고, 침투 후 공격자가 다음 단계로 나아갈 수단도 부족해집니다.
어디서부터 시작하는 게 좋을까요?
멀티스테이지 빌드로 최종 이미지에서 빌드 도구를 걷어내고, 실행 사용자를 비관리자로 바꾸는 두 가지가 가장 효과 대비 비용이 좋습니다. 그다음 distroless 베이스 이미지 적용, 읽기 전용 파일시스템, 빌드 파이프라인의 자동 스캔 순으로 넓혀가면 됩니다.

댓글 0