Docker는 컨테이너 기술의 대명사가 되었지만, 기본 설치 상태에서는 데몬이 root 권한으로 실행됩니다. 이는 개발 편의성을 제공하는 동시에, 시스템 전체를 위협할 수 있는 심각한 보안 취약점이 됩니다. 이번 시리즈에서는 Docker 19.03부터 정식 지원된 Rootless 모드가 무엇인지, 왜 도입해야 하는지부터 실무 운용 방법까지 4부에 걸쳐 상세히 다룹니다.


1. Docker 데몬이 root로 실행된다는 것의 의미

1.1 /var/run/docker.sock의 위험성

Docker를 설치하고 ps aux | grep dockerd 명령을 실행해 보면, dockerd 프로세스의 소유자가 root임을 확인할 수 있습니다.

ps aux | grep dockerd
# root  1234  ...  /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Docker 클라이언트는 이 데몬과 UNIX 소켓(/var/run/docker.sock)을 통해 통신합니다. 문제는 이 소켓의 권한입니다.

ls -la /var/run/docker.sock
# srw-rw---- 1 root docker 0 Mar 28 09:00 /var/run/docker.sock

docker 그룹에 속한 사용자는 이 소켓을 통해 데몬에 접근할 수 있습니다. 이는 곧 docker 그룹 멤버는 사실상 root와 동등한 권한을 갖는다는 의미입니다. 공식 Docker 문서도 이를 명시적으로 경고하고 있습니다.

“The Docker daemon binds to a Unix socket, not a TCP port. By default it’s the root user that owns the Unix socket, and other users can only access it using sudo. The Docker daemon always runs as the root user.”

1.2 컨테이너 탈출(Container Escape) 시나리오

가장 직관적인 위협은 컨테이너 탈출입니다. Root 모드에서는 다음과 같이 호스트 파일시스템 전체를 컨테이너에 마운트할 수 있습니다.

# 보안 위협 시연 (교육 목적)
# docker 그룹 멤버라면 누구나 실행 가능
docker run --rm -v /:/host alpine chroot /host id
# uid=0(root) gid=0(root)
# 결과: 호스트 시스템의 root 쉘 획득

이 단 한 줄의 명령으로 호스트의 /etc/passwd, /etc/shadow, SSH 키 등 모든 민감한 파일에 접근할 수 있습니다. CI/CD 파이프라인이나 공유 개발 서버에서 이러한 설정이 유지된다면 심각한 보안 위협이 됩니다.

또한 Docker 소켓 자체를 컨테이너에 마운트하는 패턴(Docker-in-Docker 대안으로 흔히 사용됨)도 위험합니다.

# docker.sock을 컨테이너에 마운트하는 위험한 패턴
docker run -v /var/run/docker.sock:/var/run/docker.sock some-ci-image
# 이 컨테이너는 호스트 Docker 데몬에 완전한 접근 권한을 가짐

2. Rootless 모드의 핵심 원리

2.1 Linux User Namespace란?

Rootless 모드의 핵심은 Linux User Namespace입니다. User Namespace는 Linux 커널 3.8(2013)에 도입된 기능으로, 프로세스에게 독립된 사용자 ID(uid) 및 그룹 ID(gid) 공간을 제공합니다.

쉽게 말해, 컨테이너 내부에서는 root(uid 0)처럼 보이지만, 호스트에서는 일반 사용자로 실행되도록 매핑하는 것입니다.

컨테이너 내부    ←→    호스트
uid 0 (root)          uid 100000 (일반 사용자)
uid 1 (daemon)        uid 100001
uid 1000 (appuser)    uid 101000

이 매핑 덕분에 컨테이너가 탈출하더라도 호스트에서는 권한이 제한된 일반 사용자에 불과합니다.

2.2 uid/gid 매핑 메커니즘

User Namespace에서의 uid 매핑은 세 개의 값으로 정의됩니다.

컨테이너 시작 uid : 호스트 시작 uid : 매핑 범위

예를 들어 0:100000:65536이라는 매핑은 다음을 의미합니다.

  • 컨테이너의 uid 0 → 호스트의 uid 100000
  • 컨테이너의 uid 1 → 호스트의 uid 100001
  • 컨테이너의 uid 65535 → 호스트의 uid 165535

이 매핑은 커널의 /proc/[pid]/uid_map 파일에서 확인할 수 있습니다.

# rootless 컨테이너 실행 후 매핑 확인
docker run -d --name test alpine sleep 3600
PID=$(cat ~/.local/share/docker/containers/$(docker ps -q -f name=test)/state/pid)
cat /proc/$PID/uid_map
# 0  100000  65536

2.3 /etc/subuid, /etc/subgid의 역할

Rootless 모드에서 사용할 수 있는 uid/gid 범위는 /etc/subuid/etc/subgid 파일에 정의됩니다.

cat /etc/subuid
# huni:100000:65536
# 형식: 사용자명:시작uid:범위크기

cat /etc/subgid
# huni:100000:65536

위 설정은 huni 사용자가 컨테이너 내부에서 65536개의 uid(100000~165535)를 사용할 수 있음을 의미합니다. 이 범위가 충분히 커야 다양한 컨테이너 이미지를 문제없이 실행할 수 있습니다.

# 현재 사용자의 subuid 설정 확인
grep $USER /etc/subuid /etc/subgid

# 컨테이너 내부 uid 확인
docker run --rm alpine id
# uid=0(root) gid=0(root)  ← 컨테이너 내부에서는 root

# 호스트에서 실제 프로세스 확인
ps aux | grep -E "rootlesskit|dockerd-rootless"
# huni  12345  ...  rootlesskit  ← 호스트에서는 일반 사용자

3. Root 모드 vs Rootless 모드 비교

3.1 기능 차이 비교표

항목 Root 모드 Rootless 모드
데몬 실행 uid 0 (root) 일반 사용자 uid
docker.sock 위치 /var/run/docker.sock /run/user/UID/docker.sock
daemon.json 위치 /etc/docker/daemon.json ~/.config/docker/daemon.json
데이터 저장 경로 /var/lib/docker ~/.local/share/docker
1024 미만 포트 바인딩 기본 가능 별도 설정 필요
overlay2 스토리지 기본 지원 Ubuntu 22.04+ 네이티브 지원, 구형은 fuse-overlayfs
cgroup v2 지원 완전 지원 완전 지원 (cgroup v2 권장)
Docker-in-Docker 완전 지원 제한적
host 네트워크 모드 지원 제한적 (슬로우)
멀티 사용자 격리 단일 데몬 공유 사용자별 독립 데몬

3.2 보안 관점 비교

보안 위협 Root 모드 영향 Rootless 모드 영향
컨테이너 탈출 시 호스트 root 권한 획득 일반 사용자 uid 범위로 제한
docker.sock 탈취 시 호스트 완전 장악 해당 사용자 권한만 영향
악성 이미지 실행 시 호스트 파일시스템 접근 가능 User Namespace로 격리
커널 취약점(CVE) 악용 root 권한 상승 가능 일반 사용자 권한 이상 상승 불가

3.3 성능 영향 분석

Rootless 모드는 네트워크 레이어에서 약간의 오버헤드가 있습니다.

  • 파일시스템 I/O: overlay2 사용 시 Root 모드와 거의 동일 (fuse-overlayfs 사용 시 약 10~15% 저하)
  • 네트워크 처리량: slirp4netns 사용 시 약 20~30% 저하, pasta 사용 시 약 5~10% 저하
  • CPU/메모리: 프로세스 구조상 rootlesskit 레이어 추가로 미미한 오버헤드

대부분의 워크로드에서 체감할 수 없는 수준이며, 보안 이점이 성능 차이를 크게 상회합니다.


4. Rootless 모드가 해결하는 보안 위협

4.1 권한 상승 공격 차단

컨테이너 내부에서 root로 실행되는 프로세스가 커널 취약점을 이용해 권한 상승을 시도하더라도, 호스트에서의 실제 uid는 일반 사용자이기 때문에 시스템 전체에 대한 피해를 최소화할 수 있습니다.

# rootless 컨테이너 내부에서 권한 확인
docker run --rm --user root alpine id
# uid=0(root) gid=0(root)  ← 컨테이너 내부

# 호스트에서 동일 프로세스 확인
ps -o uid,pid,comm | grep containerd-shim
# 100000  5678  containerd-shi  ← 호스트에서는 uid 100000 (비특권)

4.2 호스트 파일시스템 접근 제한

Rootless 모드에서는 컨테이너가 호스트의 민감한 경로를 마운트하려 해도 일반 사용자의 파일 접근 권한에 따라 제한됩니다.

# root 소유 파일에 대한 접근 시도
docker run --rm -v /etc/shadow:/shadow alpine cat /shadow
# cat: can't open '/shadow': Permission denied
# ← 일반 사용자가 /etc/shadow를 읽을 수 없으므로 마운트 자체가 차단됨

4.3 실제 CVE 사례

  • CVE-2019-5736 (runc 취약점): 컨테이너 내부에서 호스트 runc 바이너리를 덮어쓰는 공격. Root 모드에서는 호스트 root 권한을 획득하지만, Rootless 모드에서는 일반 사용자 디렉토리 내의 파일만 덮어쓸 수 있어 피해 범위가 제한됩니다.
  • CVE-2022-0185 (Linux 커널 취약점): 컨테이너 내에서 커널 취약점을 이용한 권한 상승. Rootless 환경에서는 User Namespace 경계가 추가 방어선 역할을 합니다.

5. Rootless 모드의 현실적 제약사항

Rootless 모드가 모든 상황에서 완벽한 대안은 아닙니다. 도입 전에 다음 제약사항을 반드시 확인하세요.

5.1 지원되지 않거나 제한되는 기능

기능 상태 비고
포트 1024 미만 바인딩 추가 설정 필요 sysctl 변경 또는 setcap으로 해결 가능
--net=host 네트워크 제한적 지원 slirp4netns 사용 시 실제 host 네트워크와 다름
--privileged 컨테이너 비권장 보안 격리 수준 저하
AppArmor 프로파일 미지원 Seccomp은 지원
Checkpoint/Restore (CRIU) 미지원 -
Docker-in-Docker 제한적 rootless 중첩 실행 가능하나 복잡
cgroup v1 환경 리소스 제한 불가 cgroup v2 환경 권장

5.2 포트 1024 미만 바인딩 제한

Linux에서는 기본적으로 비특권 사용자(uid != 0)가 1024 미만 포트를 바인딩할 수 없습니다. 이는 Rootless Docker에도 적용됩니다.

# 1024 미만 포트 바인딩 시도 → 실패
docker run -p 80:80 nginx
# Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: permission denied

이 제약은 Part 4에서 세 가지 방법으로 우회하는 방법을 상세히 다룹니다.

5.3 멀티 사용자 환경에서의 고려사항

Rootless 모드는 사용자별로 독립된 Docker 데몬이 실행됩니다. 이는 보안 측면에서 격리 효과가 있지만, 동시에 다음을 의미합니다.

  • 사용자 A의 이미지와 컨테이너를 사용자 B가 공유하지 못합니다.
  • 각 사용자마다 Docker 데이터(~/.local/share/docker)가 별도로 존재합니다.
  • 디스크 사용량이 사용자 수에 비례해 증가할 수 있습니다.

6. 어떤 환경에서 Rootless 모드를 도입해야 하는가?

환경 권장 여부 이유
개인 개발 서버 강력 권장 단일 사용자, 보안 이점 명확
멀티 사용자 공유 서버 권장 사용자별 격리로 상호 보안 침해 방지
금융/공공기관 규제 환경 필수 검토 보안 컴플라이언스 요구사항 충족
CI/CD 파이프라인 권장 빌드 에이전트의 권한 최소화
Kubernetes 노드 containerd/CRI-O 검토 권장 Docker 자체보다 containerd 직접 사용
1024 미만 포트 필수 + sysctl 변경 불가 Root 모드 네트워크 정책상 제약이 있는 환경

결론

Docker의 Rootless 모드는 단순한 선택적 보안 기능이 아닙니다. 컨테이너 보안의 기본값을 올바르게 설정하는 것이며, 보안 사고가 발생했을 때 그 피해 범위를 극적으로 줄여주는 핵심 방어 메커니즘입니다.

Linux User Namespace를 통한 uid/gid 매핑 원리를 이해했다면, 이제 실제 설치 단계로 넘어갈 준비가 된 것입니다. 다음 편에서는 Ubuntu 22.04/24.04 환경에서 Rootless Docker를 단계별로 설치하는 방법을 상세히 다룹니다.

참고자료


Docker Rootless Mode 시리즈

  • [Part 1] Rootless 모드란? 보안 개념과 필요성 ← 현재 글
  • [Part 2] Ubuntu에서 Rootless Docker 설치하기](/container/docker-rootless-mode-installation/)
  • [Part 3] Rootless Docker 설정 및 운용 가이드](/container/docker-rootless-mode-configuration/)
  • [Part 4] Rootless Docker 실전 활용 및 트러블슈팅](/container/docker-rootless-mode-practical-guide/)

댓글남기기