"컨테이너는 격리된 공간이니까 내부에서 root를 써도 괜찮지 않을까?"
"굳이 USER를 만들면 레이어만 늘어나고 귀찮은데..."
"컨테이너 USER가 볼륨 바인드 마운트 된 호스트의 디렉토리를 쓰게 하려면 권한 해결해주기 귀찮은데.."
많은 개발자가 이렇게 생각하며 Dockerfile의 기본값인 root 사용자를 그대로 사용합니다.
하지만 이는 보안상 매우 위험한 관행입니다. 컨테이너의 '격리'는 완벽한 방화벽이 아니며, root 권한으로 컨테이너를 실행하는 것은 서버 전체를 위험에 노출하는 지름길이 될 수 있습니다.
이 글에서는 컨테이너를 root로 실행하면 '왜' 안 되는지, 그리고 '어떻게' 이를 방지해야 하는지 명확하게 설명합니다.
1. 최악의 시나리오: "컨테이너 탈출" (Container Escape)
root 사용을 피해야 하는 가장 결정적인 이유입니다.
-
기본 매핑: 도커 컨테이너 내부의
root사용자 (UID 0)는 기본적으로 호스트(Host) 서버의root사용자 (UID 0)와 동일한 사용자입니다. -
격벽이 뚫릴 때: 만약 공격자가 애플리케이션의 취약점이나 도커, 혹은 리눅스 커널 자체의 취약점을 이용해 컨테이너의 격리된 환경을 '탈출'하는 데 성공했다고 가정해 봅시다.
-
결과: 이때 컨테이너가
root권한으로 실행 중이었다면, 공격자는 호스트 서버의root권한을 즉시 획득하게 됩니다. 서버 전체가 완전히 장악당하는 것입니다.
비유: 컨테이너를
root로 실행하는 것은 "호텔 마스터 키를 가진 손님"을 방에 재우는 것과 같습니다. 그 손님이 자기 방(컨테이너) 문을 따고 나오는 순간, 호텔 전체(호스트)를 마음대로 돌아다닐 수 있습니다.
반면, 컨테이너를 appuser(UID 1001) 같은 권한 없는 일반 사용자로 실행했다면 어떨까요? 공격자가 탈출에 성공하더라도 호스트에서 appuser라는 아무 권한 없는 사용자의 권한만 갖게 되어 피해를 최소화할 수 있습니다.
2. 최소 권한의 원칙 (Principle of Least Privilege)
보안의 가장 기본 원칙은 "모든 프로그램과 사용자는 작업을 수행하는 데 필요한 최소한의 권한만 가져야 한다"는 것입니다.
웹 애플리케이션을 실행하는 데는 root 권한이 전혀 필요하지 않습니다.
-
root일 때: 공격자가 컨테이너에 침입하면(탈출하지 못했더라도), 그는 컨테이너 안의root입니다.-
apt-get install등으로 악성 스캐너, 크립토 마이너를 마음대로 설치할 수 있습니다. -
데이터베이스 연결 정보가 담긴 설정 파일(
settings.py등)을 포함한 모든 파일을 수정하고 삭제할 수 있습니다.
-
-
appuser일 때: 공격자가 침입해도, 그는appuser일 뿐입니다.-
apt-get install? Permission Denied. -
시스템 설정 파일 수정? Permission Denied.
-
피해 범위가
appuser가 소유한 애플리케이션 소스 코드 디렉터리로 극히 제한됩니다.
-
3. 흔한 오해 바로잡기
🤔 "USER 명령어는 이미지 레이어만 늘리는 것 아닌가요?"
아닙니다. 이것은 가장 큰 오해입니다.
Dockerfile의 USER 명령어는 파일 시스템 레이어를 생성하지 않습니다. 이 명령어는 이미지의 메타데이터(Metadata) 에 "이 컨테이너가 시작될 때 기본 사용자는 'appuser'로 설정하라"는 지시어를 추가할 뿐입니다.
이미지 용량이 1KB도 늘어나지 않으며 빌드 속도에도 영향을 주지 않습니다.
물론 RUN useradd... 명령어는 사용자를 생성하며 아주 미세한 레이어를 추가하지만, 이는 보안을 위해 당연히 지불해야 할 비용입니다.
🤔 "80번 포트를 열려면 어차피 root가 필요한데요?"
맞습니다. 리눅스에서 1024 미만 포트(예: 80, 443)는 root만 열 수 있습니다. 하지만 이는 더 이상 root로 컨테이너를 실행할 이유가 되지 못합니다.
현대적인 방식은 다음과 같습니다.
-
컨테이너 내부는
appuser권한으로 8000번 같은 높은 포트로 앱을 실행합니다. -
외부에서 도커가 포트를 매핑(
docker run -p 80:8000)하거나, Nginx 같은 리버스 프록시가 외부 80번 요청을 내부 8000번 포트로 전달합니다.
컨테이너 내부는 root 권한이 전혀 필요 없습니다.
4. HOW: Dockerfile 모범 사례
그렇다면 어떻게 적용해야 할까요? Dockerfile 마지막에 다음 몇 줄을 추가하는 것은 낭비가 아니라 가장 기본적이고 중요한 보안 수칙입니다.
Dockerfile
FROM python:3.12-slim
WORKDIR /app
# ... (apt-get install, pip install 등 필요한 작업)
# 1. 시스템 권한이 없는(non-privileged) 그룹 및 사용자 생성
# -r: 시스템 사용자/그룹으로 생성, --no-create-home: 홈 디렉터리 생성 안 함
RUN groupadd -r appgroup && useradd -r -g appgroup --no-create-home appuser
# 2. 애플리케이션 파일 복사 시 소유권을 appuser로 지정
# (이후 WORKDIR의 파일들도 이 소유권을 따름)
COPY --chown=appuser:appgroup . .
# 3. 이후 명령어를 실행할 사용자를 appuser로 전환
USER appuser
# 4. 8000번 같은 높은 포트로 애플리케이션 실행
EXPOSE 8000
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
요약
컨테이너를 root로 실행하는 것은 "방어의 깊이(Defense in Depth)" 개념을 포기하는 것입니다. 격리라는 1차 방어막이 뚫렸을 때를 대비한 2차 방어막(USER)을 스스로 걷어차는 행위입니다.
지금 바로 여러분의 Dockerfile을 확인해 보세요.
댓글이 없습니다.