컨테이너 환경과 유저 권한

greenTea·2025년 6월 2일

컨테이너 시큐리티 책을 읽고 정리한 Docker 사용자 권한

최근 컨테이너 보안 관련 서적을 읽으며 Docker의 권한 모델에 대해 공부하게 되었습니다. 그동안 막연히 "컨테이너는 격리되어 있으니까 안전하겠지"라고 생각했는데, 실제로는 상당히 복잡하고 위험한 부분들이 많다는 것을 알게 되었습니다. 특히 사용자 권한과 관련된 부분에서 제가 가지고 있던 잘못된 이해들을 발견할 수 있었습니다.

이번 글에서는 제가 새롭게 배운 Docker의 사용자 권한 모델과 보안 이슈들을 정리해보겠습니다.

첫 번째 충격: Docker는 기본적으로 진짜 격리가 아니다

제가 잘못 알고 있었던 것

처음에는 Docker 컨테이너가 완전히 독립된 환경이라고 생각했습니다. 컨테이너 안의 root와 호스트의 root는 별개의 존재라고 믿고 있었죠.

# 제가 예상했던 동작
[john@localhost ~]$ docker run -it --rm ubuntu bash
root@6b94f42cb199:/# # 이 root는 가상의 root일 것이다

실제 확인해본 결과

하지만 실제로 확인해보니(책을 읽으면서) 제 생각과는 다르다는 것을 알게 되었습니다.

# 일반 사용자로 Docker 실행 (Rocky Linux 9)
[john@localhost ~]$ whoami
john

[john@localhost ~]$ docker run -it --rm ubuntu bash
root@6b94f42cb199:/# id
uid=0(root) gid=0(root) groups=0(root)

# 호스트에서 프로세스 확인
[john@localhost ~]$ ps aux | grep bash
root     12345  0.0  0.0  bash  # 실제로 호스트의 root로 실행됨!

컨테이너 내부의 root가 호스트의 실제 root와 동일한 UID 0을 가진다는 것이었습니다. 이는 Docker가 기본적으로 User Namespace를 사용하지 않기 때문입니다.

macos에선 vm환경을 통해서 실행되기에 ps로 실제 프로세스를 보면 root가 보이지 않습니다.

위험성 테스트

이것이 얼마나 위험한지 직접 테스트해봤습니다.

# 컨테이너에서 호스트 파일 생성
[john@localhost ~]$ docker run -v /tmp:/host-tmp -it --rm ubuntu bash
root@6b94f42cb199:/# touch /host-tmp/hello
root@6b94f42cb199:/# echo "hello world" > /host-tmp/hello

# 호스트에서 확인
[john@localhost ~]$ ls -la /tmp/hello
-rw-r--r-- 1 root root 21 /tmp/hello  # 진짜 root 파일!

[john@localhost ~]$ cat /tmp/hello
hello world

보시면 실제 root 파일이 생긴것을 볼 수 있습니다.(제 개인 로컬 환경에서는 루트 권한을 가진 제 이름으로 만들어져 있었습니다.)

두 번째 발견: Capabilities의 존재

root인데 왜 모든 걸 할 수 없을까?

컨테이너의 root가 호스트의 실제 root라면, 왜 일부 시스템 작업이 안 되는지 궁금했습니다.

# 일반 컨테이너에서
[john@localhost ~]$ docker run -it --rm ubuntu bash
root@6b94f42cb199:/# mount /dev/sda1 /mnt
mount: /mnt: permission denied  # 왜 안 될까?

root@6b94f42cb199:/# insmod malicious.ko
bash: insmod: command not found

Capabilities라는 개념

책에서 배운 건데, Linux는 root 권한을 세분화한 Capabilities라는 시스템을 사용합니다.

# 호스트 root의 capabilities (Rocky Linux 9)
[john@localhost ~]$ sudo capsh --print
Current: =ep

# 컨테이너 root의 capabilities
[john@localhost ~]$ docker run -it --rm ubuntu bash
root@6b94f42cb199:/# capsh --print  
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip

Docker는 제한된 capabilities를 가진 root를 제공하는 것이었습니다.
(우분투에서는 apt update && apt install -y 이거를 설치하시고 나면 볼 수 있습니다.)

--privileged 옵션의 위험성

# 모든 capabilities 활성화
[john@localhost ~]$ docker run --privileged -it --rm ubuntu bash
root@6b94f42cb199:/# capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+eip

root@6b94f42cb199:/# mount /dev/sda1 /mnt  # 이제 가능!
root@6b94f42cb199:/# fdisk -l  # 호스트 디스크 정보 모두 보임

--privileged는 사실상 호스트와 동일한 권한을 주는 매우 위험한 옵션이었습니다.

세 번째 깨달음: --user 옵션의 진실

사용자 지정으로 안전해질까?

안전한 컨테이너 실행을 위해 --user 옵션을 사용해봤습니다.

[john@localhost ~]$ docker run --user 1000:1000 -it --rm ubuntu bash
I have no name!@da7c8f9b6c12:/$ id
uid=1000 gid=1000

# 호스트에서 확인
[john@localhost ~]$ ps aux | grep bash
john    12345  ... bash  # 이제 john으로 실행됨!

볼륨 마운트에서 발생하는 권한 문제

하지만 실제 사용하다 보니 권한 문제가 발생했습니다.

# 호스트에서 root 파일 생성
[john@localhost ~]$ sudo touch /tmp/secret_file
[john@localhost ~]$ sudo chmod 600 /tmp/secret_file
[john@localhost ~]$ ls -la /tmp/secret_file
-rw------- 1 root root 0 /tmp/secret_file

# --user 1000:1000 컨테이너에서 접근
[john@localhost ~]$ docker run --user 1000:1000 -v /tmp:/host-tmp -it --rm ubuntu bash
I have no name!@da7c8f9b6c12:/$ cat /host-tmp/secret_file
cat: /host-tmp/secret_file: Permission denied  # 접근 불가!

이는 컨테이너의 사용자가 호스트의 동일한 UID와 완전히 같은 권한을 가지기 때문이었습니다.

네 번째 학습: 진짜 격리 방법

User Namespace 활성화

Docker에서 진짜 격리를 하려면 User Namespace를 활성화해야 합니다.

# /etc/docker/daemon.json
{
  "userns-remap": "default"
}

안전한 사용 패턴들

그 외에 안전한 사용 패턴들을 정리해봤습니다.

# 1. 최소 권한 원칙
[john@localhost ~]$ docker run --user 1000:1000 --cap-drop=ALL --security-opt=no-new-privileges app

# 2. 읽기 전용 파일시스템
[john@localhost ~]$ docker run --read-only --tmpfs /tmp app

# 3. 리소스 제한
[john@localhost ~]$ docker run --memory=512m --pids-limit=100 app

정리하며

컨테이너 보안 책을 읽으며 알게 된 가장 중요한 깨달음들:

  1. Docker는 기본적으로 완전한 격리가 아니다 - 컨테이너의 root는 호스트의 실제 root
  2. Capabilities로 일부 제한이 있지만 여전히 위험하다 - 파일시스템 권한은 동일
  3. --user 옵션은 단순히 실행 사용자만 바꾼다 - User Namespace 분리와는 다름
  4. 진짜 격리를 위해서는 User Namespace나 rootless Docker를 사용해야 한다
  5. 권한 문제는 UID 숫자로 결정된다 - 사용자 이름은 의미 없음

앞으로는 Docker를 사용할 때 이런 보안 원칙들을 꼭 고려해서 사용해야겠습니다. 특히 프로덕션 환경에서는 더욱 신중하게 권한 설정을 해야겠다고 느꼈습니다.

참고자료

container security

profile
greenTea입니다.

0개의 댓글