사용자 네임스페이스로 컨테이너 격리하기

사용자 네임스페이스로 컨테이너 격리하기

리눅스 네임스페이스는 실행 중인 프로세스를 격리해서, 프로세스 자신이 그 제한을 인지하지 못한 채 시스템 리소스에 대한 접근을 제한하는 역할을 해요. 컨테이너 내부에서 발생할 수 있는 권한 상승(privilege-escalation) 공격을 막는 가장 좋은 방법은, 컨테이너의 애플리케이션이 비특권 사용자로 실행되도록 설정하는 거예요. 그런데 컨테이너 안에서 프로세스가 어쩔 수 없이 root 사용자로 실행되어야 하는 경우가 있죠? 이때 그 사용자를 Docker 호스트의 덜 특권을 가진 사용자로 다시 매핑(remap)할 수 있어요. 매핑된 사용자에게는 네임스페이스 안에서 0부터 65536까지의 일반 UID처럼 동작하는 UID 범위가 할당되지만, 호스트 머신 자체에서는 아무런 권한도 갖지 못하게 됩니다.

참고

userns-remap을 사용해도 Docker 데몬 자체는 여전히 root로 실행돼요. 데몬과 컨테이너 모두를 root 권한 없이 실행하려면 Rootless mode 문서를 대신 확인해 주세요.

출처: 공식문서

리매핑과 하위 사용자·그룹 ID 이해하기

리매핑 자체는 /etc/subuid/etc/subgid라는 두 개의 파일로 처리됩니다. 두 파일은 동일한 방식으로 동작하는데, 하나는 사용자 ID 범위를, 다른 하나는 그룹 ID 범위를 담당해요. /etc/subuid에 다음과 같은 항목이 있다고 가정해 볼게요.

testuser:231072:65536

이 의미는 testuser에게 하위 사용자 ID 범위로 231072부터 시작해서 연속된 65536개의 정수가 할당되었다는 뜻이에요. UID 231072는 네임스페이스 안(여기서는 컨테이너 안)에서 UID 0(root)으로 매핑되고, UID 231073은 UID 1로 매핑되는 식이죠. 만약 프로세스가 네임스페이스 밖으로 권한을 상승시키려고 시도한다면, 그 프로세스는 호스트에서 아무런 실제 사용자와도 매핑되지 않는 높은 번호의 비특권 UID로 실행되고 있어요. 즉, 호스트 시스템에서 전혀 권한을 갖지 못한다는 뜻입니다.

참고

같은 사용자나 그룹에 대해 /etc/subuid/etc/subgid 파일에 겹치지 않는 매핑을 여러 개 추가하면, 하위 범위를 여러 개 할당하는 것도 가능해요. 다만 이 경우 Docker는 커널의 제한( /proc/self/uid_map/proc/self/gid_map에 최대 5개 항목만 허용)에 따라 처음 5개 매핑만 사용합니다.

Docker에서 userns-remap 기능을 구성할 때, 기존 사용자나 그룹을 지정하거나 default를 지정할 수 있어요. default를 지정하면 dockremap이라는 사용자와 그룹이 생성되어 이 용도로 사용됩니다.

경고

일부 리눅스 배포판에서는 새 그룹이 /etc/subuid/etc/subgid 파일에 자동으로 추가되지 않아요. 그런 경우에는 직접 파일을 편집해서 겹치지 않는 범위를 할당해야 할 수 있어요. 이 과정은 사전 준비 사항에서 다룹니다.

범위가 서로 겹치지 않는 것이 매우 중요해요. 그래야 프로세스가 다른 네임스페이스에 접근할 수 없으니까요. 대부분의 리눅스 배포판에서는 사용자나 그룹을 추가하거나 제거할 때 시스템 유틸리티가 이 범위를 자동으로 관리해 줍니다.

이 리매핑은 컨테이너 입장에서는 투명하게 보이지만, 컨테이너가 Docker 호스트의 리소스에 접근해야 하는 상황(예: 시스템 사용자가 쓸 수 없는 파일시스템 영역에 바인드 마운트하는 경우)에서는 구성이 다소 복잡해질 수 있어요. 보안 관점에서는 이런 상황 자체를 피하는 것이 가장 좋습니다.

사전 준비 사항

  1. 하위 UID와 GID 범위는 기존 사용자와 연결되어 있어야 해요. 비록 그 연결이 구현 세부사항처럼 보일지라도 말이죠. 그 사용자가 /var/lib/docker/ 아래의 네임스페이스 스토리지 디렉터리를 소유하게 됩니다. 기존 사용자를 쓰고 싶지 않다면 Docker가 사용자를 생성해서 사용할 수도 있어요. 기존 사용자 이름이나 사용자 ID를 사용하려면 그 사용자가 이미 존재해야 해요. 일반적으로는 /etc/passwd/etc/group에 해당 항목이 있어야 한다는 뜻이지만, 다른 인증 백엔드를 사용 중이라면 이 요구사항이 다르게 해석될 수도 있어요.

    이를 확인하려면 id 명령을 사용하세요.

    $ id testuser
    
    uid=1001(testuser) gid=1001(testuser) groups=1001(testuser)
    
  2. 호스트에서 네임스페이스 리매핑을 처리하는 방식은 /etc/subuid/etc/subgid 두 파일을 통해서예요. 이 파일들은 보통 사용자나 그룹을 추가·제거할 때 자동으로 관리되지만, 일부 배포판에서는 직접 관리해야 할 수도 있어요.

    각 파일에는 세 개의 필드가 들어 있어요. 사용자의 이름 또는 ID, 그다음에 시작 UID 또는 GID(네임스페이스 안에서는 UID 또는 GID 0으로 취급됨), 마지막으로 해당 사용자에게 제공되는 최대 UID 또는 GID 개수입니다. 예를 들어 다음 항목을 보면,

    testuser:231072:65536
    

    testuser가 시작한 사용자 네임스페이스 프로세스는 호스트 UID 231072(네임스페이스 안에서는 UID 0처럼 보임)부터 296607(231072 + 65536 - 1)까지 소유하게 된다는 뜻이에요. 이 범위는 서로 겹치지 않아야 해요. 그래야 네임스페이스 프로세스가 서로의 네임스페이스에 접근할 수 없으니까요.

    사용자를 추가한 후 /etc/subuid/etc/subgid를 확인해서 사용자 항목이 각각 있는지 살펴보세요. 없다면 겹치지 않도록 주의해서 직접 추가해야 합니다.

    Docker가 자동으로 생성하는 dockremap 사용자를 사용하려면, Docker를 구성하고 재시작한 후 이 파일들에서 dockremap 항목을 확인하세요.

  3. Docker 호스트에서 비특권 사용자가 쓰기 작업을 해야 하는 위치가 있다면, 해당 위치의 권한을 그에 맞게 조정하세요. Docker가 자동 생성하는 dockremap 사용자를 사용하려는 경우에도 마찬가지예요. 다만 이 경우에는 Docker를 구성하고 재시작한 후에 권한을 수정할 수 있어요.

  4. userns-remap을 활성화하면 기존 이미지와 컨테이너 레이어, 그리고 /var/lib/docker/ 안의 다른 Docker 객체들이 사실상 가려집니다. Docker가 이 리소스들의 소유권을 조정해야 하고, 실제로는 /var/lib/docker/ 안의 하위 디렉터리에 저장하기 때문이에요. 이 기능은 기존 Docker 설치보다는 새 Docker 설치에서 활성화하는 것이 가장 좋아요.

    마찬가지로 userns-remap을 비활성화하면, 활성화된 동안 생성된 리소스에는 접근할 수 없게 됩니다.

  5. 사용자 네임스페이스의 제한 사항을 확인해서 자신의 사용 사례가 가능한지 확인하세요.

데몬에서 userns-remap 활성화하기

dockerd--userns-remap 플래그와 함께 시작하거나, daemon.json 구성 파일을 사용해서 데몬을 구성할 수 있어요. daemon.json 방식이 권장됩니다. 플래그를 사용한다면 다음 명령을 모델로 삼으세요.

$ dockerd --userns-remap="testuser:testuser"
  1. /etc/docker/daemon.json을 편집하세요. 파일이 이전에 비어 있었다고 가정하면, 다음 항목은 testuser라는 사용자와 그룹을 사용해서 userns-remap을 활성화합니다. 사용자와 그룹을 ID나 이름으로 지정할 수 있어요. 그룹 이름이나 ID가 사용자 이름이나 ID와 다를 때만 그룹을 지정하면 됩니다. 사용자와 그룹 이름 또는 ID를 모두 제공하려면 콜론(:) 문자로 구분하세요. testuser의 UID와 GID가 1001이라고 가정할 때, 다음 형식들이 모두 유효한 값이에요.

    • testuser
    • testuser:testuser
    • 1001
    • 1001:1001
    • testuser:1001
    • 1001:testuser
    {
     "userns-remap": "testuser"
    }
    

    참고

    dockremap 사용자를 사용하고 Docker가 직접 생성하게 하려면, 값을 testuser 대신 default로 설정하세요.

    파일을 저장하고 Docker를 재시작하세요.

  2. dockremap 사용자를 사용 중이라면, id 명령으로 Docker가 사용자를 생성했는지 확인하세요.

    $ id dockremap
    
    uid=112(dockremap) gid=116(dockremap) groups=116(dockremap)
    

    /etc/subuid/etc/subgid에 항목이 추가되었는지도 확인하세요.

    $ grep dockremap /etc/subuid
    
    dockremap:231072:65536
    
    $ grep dockremap /etc/subgid
    
    dockremap:231072:65536
    

    이 항목들이 없다면 root 사용자로 파일을 편집해서, 가장 높게 할당된 UID와 GID에 오프셋(이 경우 65536)을 더한 값을 시작 UID와 GID로 할당하세요. 범위가 겹치지 않도록 주의해야 해요.

  3. docker image ls 명령으로 이전 이미지들이 더 이상 보이지 않는지 확인하세요. 출력 결과는 비어 있어야 합니다.

  4. hello-world 이미지에서 컨테이너를 시작하세요.

    $ docker run hello-world
    
  5. /var/lib/docker/ 안에 네임스페이스 사용자의 UID와 GID로 이름 지어진 네임스페이스 디렉터리가 존재하고, 그 UID와 GID가 소유하며, 그룹이나 다른 사용자가 읽을 수 없는지 확인하세요. 일부 하위 디렉터리는 여전히 root가 소유하고 있고 다른 권한을 가지고 있어요.

    $ sudo ls -ld /var/lib/docker/231072.231072/
    
    drwx------ 11 231072 231072 11 Jun 21 21:19 /var/lib/docker/231072.231072/
    
    $ sudo ls -l /var/lib/docker/231072.231072/
    
    total 14
    drwx------ 5 231072 231072 5 Jun 21 21:19 aufs
    drwx------ 3 231072 231072 3 Jun 21 21:21 containers
    drwx------ 3 root root 3 Jun 21 21:19 image
    drwxr-x--- 3 root root 3 Jun 21 21:19 network
    drwx------ 4 root root 4 Jun 21 21:19 plugins
    drwx------ 2 root root 2 Jun 21 21:19 swarm
    drwx------ 2 231072 231072 2 Jun 21 21:21 tmp
    drwx------ 2 root root 2 Jun 21 21:19 trust
    drwx------ 2 231072 231072 3 Jun 21 21:21 volumes
    

    디렉터리 목록은 사용 중인 컨테이너 스토리지 드라이버에 따라 차이가 있을 수 있어요. 특히 aufs가 아닌 다른 드라이버를 사용한다면 더 그렇습니다.

    리매핑된 사용자가 소유한 디렉터리들은 /var/lib/docker/ 바로 아래의 같은 이름 디렉터리들 대신 사용됩니다. 사용되지 않는 버전(예시에서 /var/lib/docker/tmp/ 같은 것)은 제거해도 돼요. userns-remap이 활성화된 동안 Docker는 그것들을 사용하지 않습니다.

컨테이너에 대한 네임스페이스 리매핑 비활성화하기

데몬에서 사용자 네임스페이스를 활성화하면, 기본적으로 모든 컨테이너가 사용자 네임스페이스를 활성화한 상태로 시작돼요. 특권(privileged) 컨테이너 같은 일부 상황에서는 특정 컨테이너에 대해 사용자 네임스페이스를 비활성화해야 할 수도 있어요. 이런 제한 사항 중 일부는 사용자 네임스페이스 알려진 제한 사항에서 확인할 수 있습니다.

특정 컨테이너에 대해 사용자 네임스페이스를 비활성화하려면 docker container create, docker container run, 또는 docker container exec 명령에 --userns=host 플래그를 추가하세요.

이 플래그를 사용할 때 부작용이 하나 있어요. 해당 컨테이너에서는 사용자 리매핑이 활성화되지 않지만, 읽기 전용(이미지) 레이어는 컨테이너 간에 공유되므로 컨테이너 파일시스템의 소유권은 여전히 리매핑됩니다.

즉, 컨테이너 파일시스템 전체가 --userns-remap 데몬 구성에 지정된 사용자(위 예시에서는 231072)의 소유가 된다는 뜻이에요. 이로 인해 컨테이너 안의 프로그램이 예상치 못한 동작을 할 수 있어요. 예를 들어 sudo(자신의 바이너리가 사용자 0의 소유인지 확인함)나 setuid 플래그가 있는 바이너리 같은 경우가 그렇습니다.

사용자 네임스페이스 알려진 제한 사항

다음 표준 Docker 기능들은 사용자 네임스페이스가 활성화된 Docker 데몬과 호환되지 않아요.

  • 호스트와 PID 또는 NET 네임스페이스 공유(--pid=host 또는 --network=host)
  • 데몬 사용자 매핑을 인식하지 못하거나 사용할 수 없는 외부(볼륨 또는 스토리지) 드라이버
  • --userns=host를 함께 지정하지 않은 상태에서 docker run--privileged 모드 플래그 사용

사용자 네임스페이스는 고급 기능이고 다른 기능들과의 조정이 필요해요. 예를 들어 호스트에서 볼륨을 마운트하는 경우, 볼륨 내용에 대한 읽기 또는 쓰기 접근이 필요하다면 파일 소유권을 미리 조정해 두어야 합니다.

사용자 네임스페이스 컨테이너 프로세스 안의 root 사용자는 컨테이너 내부에서 슈퍼유저가 가질 것으로 기대되는 많은 권한을 갖고 있지만, 리눅스 커널은 이 프로세스가 사용자 네임스페이스 프로세스라는 내부 지식을 바탕으로 제한을 가해요. 주목할 만한 제한 중 하나는 mknod 명령을 사용할 수 없다는 점이에요. root 사용자가 컨테이너 안에서 디바이스를 생성하려고 하면 권한이 거부됩니다.

더 알아보기