본문 바로가기
일반IT

도커 컨테이너 속 'root'는 진짜 'root'가 아닐 수 있다? 🤔 UID와 Capability 이야기

by gasbugs 2025. 8. 15.

안녕하세요, 컨테이너 기술에 푹 빠진 여러분! 🐳 오늘은 도커를 사용하면서 한 번쯤은 가져봤을 법한 궁금증에 대해 깊이 파고들어 보려고 합니다. 바로 "컨테이너 안에서 root 계정으로 명령어를 실행하면, 이게 호스트 시스템의 root 권한과 같은 걸까?" 라는 질문입니다.

결론부터 말씀드리면, "아니요, 다릅니다!" 입니다. 많은 분들이 도커가 모든 것을 완벽하게 격리해 줄 것이라고 생각하지만, 사용자 ID(UID)와 권한(Privilege)의 세계는 조금 더 복잡합니다. 도커는 UID를 직접 격리하는 대신, 리눅스의 'Capability'라는 멋진 기능으로 권한을 세분화하여 보안을 유지합니다.

자, 그럼 지금부터 그 원리를 차근차근 파헤쳐 보겠습니다! 🕵️‍♂️

https://medium.com/@laxman.pokhrel.101/securing-your-docker-containers-why-and-how-to-use-a-non-root-user-f4dab2af0519

 


UID의 함정: 컨테이너와 호스트의 연결 고리

리눅스 시스템에서 사용자와 프로세스는 UID(User ID) 라는 숫자로 식별됩니다. 가장 강력한 힘을 가진 root 사용자는 관습적으로 UID 0을 부여받죠.

여기서 중요한 포인트가 나옵니다. 기본적으로 도커 컨테이너는 별도의 사용자 네임스페이스(User Namespace) 없이 실행됩니다. 이게 무슨 의미일까요? 컨테이너 내부에서 UID 0으로 실행되는 프로세스는, 호스트(Host) 운영체제 외부에서도 똑같이 UID 0으로 보인다는 뜻입니다.

예를 들어, 컨테이너 안에서 root 사용자가 어떤 파일을 생성했다고 상상해 보세요. 만약 이 파일이 볼륨 마운트를 통해 호스트와 공유된 디렉토리에 저장된다면, 호스트에서 해당 파일의 소유자는 누구일까요? 바로 호스트의 root 사용자입니다! 😱

이것이 바로 'UID가 격리되지 않는다'는 말의 의미입니다. 만약 컨테이너가 어떤 식으로든 탈출(Container Escape)에 성공한다면, 내부의 root 프로세스가 호스트의 root 권한을 그대로 행사할 수 있는 심각한 보안 위협이 될 수 있습니다.


진정한 권한 분리자: 리눅스 Capability의 등장 ✨

그렇다면 도커는 이 위험을 어떻게 관리할까요? 여기서 바로 리눅스 Capability가 등판합니다!

과거 유닉스 시스템에서는 권한이 '전부 아니면 전무(All or Nothing)' 식이었습니다. UID 0인 root는 모든 것을 할 수 있고, 그 외의 사용자는 제한된 권한만 가졌죠. 하지만 이런 방식은 보안에 매우 취약했습니다. 단지 네트워크 포트를 열기 위해 전체 프로세스에 root 권한을 주는 것은 너무 위험하니까요.

그래서 등장한 것이 Capability입니다. Capability는 root 사용자가 가진 막강한 권한을 여러 개의 작은 조각으로 나눈 것입니다. 예를 들면 이런 것들이죠.

  • CAP_NET_BIND_SERVICE: 1024번 미만의 '잘 알려진' 포트(Well-known port)에 바인딩할 수 있는 능력
  • CAP_SYS_TIME: 시스템 시간을 변경할 수 있는 능력
  • CAP_SYS_CHROOT: chroot 명령을 사용하여 루트 디렉토리를 변경할 수 있는 능력
  • CAP_KILL: 임의의 프로세스에 시그널을 보낼 수 있는 능력 (주로 프로세스를 종료시킬 때)

도커는 컨테이너를 실행할 때, 이 Capability들을 최소한으로만 부여합니다. 즉, 컨테이너 안의 root 사용자는 UID는 0이지만, root가 가진 모든 Capability를 다 가지고 있지는 않은 '반쪽짜리 root' 인 셈입니다. 👑💔

컨테이너 운영에 굳이 필요하지 않은 위험한 권한들, 예를 들어 커널 모듈을 로드하거나 시스템 전체를 재부팅하는 등의 Capability는 기본적으로 제거됩니다. 덕분에 컨테이너 내부의 root는 호스트 시스템에 심각한 손상을 입히는 대부분의 작업을 수행할 수 없게 됩니다.


더 강력한 격리를 원한다면: 사용자 네임스페이스(User Namespaces)

"그래도 UID 0이 호스트에 그대로 노출되는 건 찝찝해요!" 라고 생각하는 보안 전문가분들도 계실 겁니다. 맞습니다. 그래서 더 강력한 격리 수준을 위해 등장한 것이 바로 사용자 네임스페이스(User Namespaces) 입니다.

사용자 네임스페이스를 활성화하면, 컨테이너 내부의 UID와 호스트의 UID를 매핑(mapping)할 수 있습니다. 예를 들어, 컨테이너 안에서는 UID 0 (root) 이지만, 호스트에서는 UID 100000 과 같은 일반 사용자 ID로 보이게 만드는 거죠.

  • 컨테이너 내부: root (UID 0)
  • 호스트 외부: nobody (UID 100000)

이렇게 되면, 설령 컨테이너 프로세스가 탈출하더라도 호스트에서는 권한이 거의 없는 일반 사용자일 뿐이므로 시스템에 가할 수 있는 위협이 극적으로 줄어들게 됩니다. 👍


정리하며: 계층적인 도커 보안 모델

지금까지의 이야기를 정리해 볼까요?

  1. UID는 격리되지 않는다: 기본적으로 컨테이너의 root (UID 0)는 호스트의 root (UID 0)와 동일한 ID를 갖는다. 이는 잠재적인 보안 위협이 될 수 있다.
  2. Capability로 권한을 제한한다: 도커는 root의 권한을 잘게 쪼갠 Capability 중 꼭 필요한 것들만 컨테이너에 부여하여, 내부 root가 할 수 있는 일을 제한한다. 이것이 도커의 기본적인 보안 장치다.
  3. 사용자 네임스페이스로 UID를 격리한다: 더 높은 수준의 보안을 위해 사용자 네임스페이스를 사용하여 컨테이너의 UID 0을 호스트의 일반 사용자 UID로 매핑할 수 있다.

결국 도커의 보안은 하나의 기술이 아닌, 여러 계층의 보호 장치로 이루어져 있는 셈입니다. 우리가 무심코 사용하던 docker run 명령어 뒤에는 이처럼 깊은 고민과 똑똑한 기술들이 숨어있답니다.

이제 컨테이너 속 root가 왜 진짜 root가 아닌지, 그리고 도커가 어떻게 권한을 나누어 우리 시스템을 안전하게 지켜주는지 이해되셨나요? 😊


도커, 컨테이너, 리눅스, UID, Capability, 사용자 네임스페이스, 보안