Linux(Centos or RHEL)/docker

namespace를 이용하여 컨테이너들을 고립시켜보자

최소양 2020. 4. 21. 15:46

해당 글을 아래의 튜토리얼을 테스트 해 본 것입니다.

https://www.katacoda.com/courses/docker-security/userns-user-namespaces

 

User Namespaces | Docker Security | Katacoda

Learn the latest technologies with our hands-on labs

www.katacoda.com

 

배경지식 linux에서의 namespace?

리눅스에서 namespace는 lightweight 가상화 솔루션이다. XEN이나 KVM 같은 가상화 솔루션들은 커널 인스턴스들을 생성하여 동작시키는 것에 반하여 리눅스의 namespace는 커널 인스턴스를 만들지 않고 기존의 리소스들을 필요한 만큼의 namespace로 분리하여 묶어 관리하는 방법으로 사용한다.

자세한 내용은 아래를 확인해 주세요.

http://jake.dothome.co.kr/namespace/

 

Namespace

  Namespace 관리 리소스 리눅스에서 namespace는 lightweight 가상화 솔루션이다. XEN이나 KVM 같은 가상화 솔루션들은 커널 인스턴스들을 생성하여 동작시키는 것에 반하여 리눅스의 namespace는 커널 인스턴스를 만들지 않고 기존의 리소스들을 필요한 만큼의 namespace로 분리하여 묶어 관리하는 방법으로 사용한다. 리눅스의 cgroup과 namespace를 사용하여 container를 생성하여 사용하는 LXC, 그리고 LX

jake.dothome.co.kr

 

 

 

기본값에서는 도커 컨테이너는 root 권한으로 작동합니다. 따라서 컨테이너들도 따로 지정을 해 주지 않는이상, root 권한을 갖습니다. 따라서 도커 데몬을 실행하고있는 호스트의 파일들을 조작하는 것도 가능합니다. 이런 것을 방지하기 위해서 도커에서 리눅스의 namespace기능을 이용하여 권한을 제한하는 것을 해 보겠습니다.

 

 

Step 1. 도커 데몬을 실행하고 있는 유저 확인

1. 도커 데몬을 실행하고 있는 유저를 확인해 봅니다.

[root@centos77 ~]# ps aux | grep docker

root 1263 0.4 3.5 580196 66916 ? Ssl 13:09 0:00 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

root 3351 0.0 0.0 116880 1024 pts/0 R+ 13:11 0:00 grep --color=auto docker

[root@centos77 ~]#

 

위에서 확인할 수 있듯이 root에서 도커 데몬을 실행하고 있습니다.

 

 

2. 컨테이너 안의 권한을 확인해 봅니다.

[root@centos77 ~]# docker run --rm alpine id

uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

[root@centos77 ~]#

 

컨테이너 안의 권한은 root로 나옵니다.

 

 

3. 도커 데몬을 실행중인 호스트에서 바이너리 파일을 하나 백업해 놓습니다.

[root@centos77 ~]# cp /bin/touch /bin/touch.bak && ls -alh /bin/touch.bak

-rwxr-xr-x. 1 root root 62K 421 13:17 /bin/touch.bak

[root@centos77 ~]#

 

 

 

4. 컨테이너에서 호스트의 바이너리 파일을 삭제하는 작업을 해 봅니다.

[root@centos77 ~]# docker run -it -v /bin/:/host/ alpine rm -f /host/touch.bak

[root@centos77 ~]#

 

docker run명령어의 v옵션은 컨테이너에 볼륨을 bind mount하는 옵션입니다. 위의 명령어에서는 호스트의 ”/bin” 폴더를 컨테이너의 “/host”bind mount한다는 뜻입니다. 자세한 사항은 아래의 도커 홈페이지에서 확인할 수 있습니다.

https://docs.docker.com/engine/reference/commandline/run/#mount-volume--v---read-only

 

docker run

Description Run a command in a new container Usage docker run [OPTIONS] IMAGE [COMMAND] [ARG...] Extended description The docker run command first creates a writeable container layer over the specified...

docs.docker.com

 

 

5. 호스트에서 파일이 삭제되었는가를 확인해 봅니다.

[root@centos77 ~]# ls -alh /bin/touch.bak

ls: cannot access /bin/touch.bak: 그런 파일이나 디렉터리가 없습니다

[root@centos77 ~]#

 

파일이 없어졌습니다... 이를 방지하는 대책을 아래에서 다루어보기로 해 보겠습니다.

 

 

Step 2. 컨테이너의 유저를 변경해봅니다.

1. 컨테이너를 1000:1000 유저로서 기동시킵니다.

[root@centos77 ~]# docker run --rm --user 1000:1000 alpine id

uid=1000 gid=1000

[root@centos77 ~]#

 

uidgid1000인 것을 확인할 수 있습니다.

 

 

2. 위의 유저로서 호스트의 바이너리 파일을 삭제해 봅니다.

[root@centos77 ~]# docker run --user 1000:1000 -it --rm -v /bin:/host/ alpine rm -f /host/touch.bak

rm: can't remove '/host/touch.bak': Permission denied

[root@centos77 ~]#

 

Permission denied로 인해 삭제가 불가능합니다.

 

 

 

Step 3. user namespace 활성화

1. 도커 데몬의 설정을 할 수 있는 “/etc/docker/daemon.json”파일을 다음과 같이 설정해 줍니다.

[root@centos77 ~]# cat /etc/docker/daemon.json

{

"bip":"172.18.0.1/24",

"debug": true,

"storage-driver":"overlay2",

"userns-remap": "1000:1000"

}

[root@centos77 ~]#

 

2. 그리고 도커 데몬을 재시작해 줍니다.

[root@centos77 ~]# systemctl restart docker

[root@centos77 ~]#

 

이 지점에서 가끔 오류가 날 수도 있습니다. 그런데 오류가 어떤 것 때문에 나는지 정확하게 알려주지 않는 경우가 있습니다...

 

------

421 14:07:44 centos77.linux systemd[1]: docker.service: main process exited, code=exited, status=1/FAILURE

421 14:07:44 centos77.linux systemd[1]: Failed to start Docker Application Container Engine.

421 14:07:44 centos77.linux systemd[1]: Unit docker.service entered failed state.

421 14:07:44 centos77.linux systemd[1]: docker.service failed.

421 14:07:46 centos77.linux systemd[1]: docker.service holdoff time over, scheduling restart.

421 14:07:46 centos77.linux systemd[1]: Stopped Docker Application Container Engine.

421 14:07:46 centos77.linux systemd[1]: Starting Docker Application Container Engine...

------

이건 또 무슨 에러인가..

 

이래저래 찾아서 테스트를 해본 결과, “daemon.json”파일에서 지정한 "userns-remap" 옵션의 "1000:1000" 값에 해당되는 유저를 “/etc/subuid“”/etc/subgid“에서 찾지 못하는 경우에 도커 데몬의 재시작이 에러가 발생하는 것을 알 수 있었습니다.

 

아래와 같이 설정들을 확인해 봅니다.

 

1] 1000uidgid가 있는가

[root@centos77 ~]# cat /etc/passwd

centos77:x:1000:1000:centos77:/home/centos77:/bin/bash

[root@centos77 ~]#

 

2] 해당 유저의 네임 스페이스 매핑이 되어있는가( /etc/subuid, /etc/subgid 에 유저의 설정이 되어있는가)

[root@centos77 ~]# cat /etc/subuid

centos77:100000:65536

[root@centos77 ~]# cat /etc/subgid

centos77:100000:65536

[root@centos77 ~]#

 

위의 ”2]“에 대한 간단한 설명을 하면,

위의 파일들에 대한 설명은 아래의 도커 설명서를 보거나, ”man subuid“, ”man subgid“, ” man user_namespaces“ 들의 man 페이지를 확인해 보시면 될 것 같습니다.

https://docs.docker.com/engine/security/userns-remap/

 

Isolate containers with a user namespace

Linux namespaces provide isolation for running processes, limiting their access to system resources without the running process being aware of the limitations. For more information on Linux namespaces, see Linux...

docs.docker.com

 

 

3. 도커 데몬의 재시작이 잘 되었다면, 도커 데몬의 root 디렉토리를 확인해 봅니다.

[root@centos77 ~]# docker info | grep "Root Dir"

Docker Root Dir: /var/lib/docker/100000.100000

[root@centos77 ~]#

 

위와 같이 user namespace를 활성화하기 전의 도커 데몬의 root 디렉토리는 ”/var/lib/docker“입니다. 하지만 위와같이 user namespace를 활성화 해 준 결과, 아래와 같이 ‘100000.100000”이라는 폴더가 생긴 것을 확인할 수 있습니다.

 

[root@centos77 ~]# ll /var/lib/docker

합계 4

drwx------. 14 100000 100000 182 421 14:19 100000.100000

drwx------. 2 root root 24 420 13:45 builder

drwx--x--x. 4 root root 92 420 13:45 buildkit

drwx------. 3 root root 78 421 13:36 containers

drwx------. 3 root root 22 420 13:45 image

drwxr-x---. 3 root root 19 420 13:45 network

drwx------. 13 root root 4096 421 13:56 overlay2

drwx------. 4 root root 32 420 13:45 plugins

drwx------. 2 root root 6 421 13:56 runtimes

drwx------. 2 root root 6 420 13:45 swarm

drwx------. 2 root root 6 421 13:56 tmp

drwx------. 2 root root 6 420 13:45 trust

drwx------. 2 root root 25 420 13:45 volumes

[root@centos77 ~]#

 

 

 

Step 4. user namespace 동작 확인

컨테이너를 가동시켜서 컨테이너의 id를 확인해 봅니다.

[root@centos77 docker]# docker run --rm alpine id

uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

[root@centos77 docker]#

 

컨테이너 내의 유저가 root인 것을 확인할 수가 있습니다.

 

위의 명령어를 실행하였을 경우 아래와 같은 에러가 발생할 경우가 있습니다..

 

[root@centos77 ~]# docker run --rm alpine id

docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "process_linux.go:319: getting the final child's pid from pipe caused \"EOF\"": unknown.

[root@centos77 ~]#

 

해당 에러는 리눅스 커널파라미터의 user.max_user_namespaces를 설정하지 않아서 발생한 에러일 가능성이 있습니다. 그 외에 다른 문제도 있을 가능성이 있지만, 리눅스 베포판 마다 설정이 조금씩 다를 수 있으니, 아래의 링크를 보고 자신에게 맞는 리눅스 베포판을 찾아서 조치를 취해줍니다.

https://docs.docker.com/engine/security/rootless/#distribution-specific-hint

 

Run the Docker daemon as a non-root user (Rootless mode)

Rootless mode allows running the Docker daemon and containers as a non-root user, for the sake of mitigating potential vulnerabilities in the daemon and the container runtime. Rootless mode does...

docs.docker.com

 

 

2. 도커 데몬을 실행하고 있는 호스트의 바이너리 파일을 컨테이너에서 삭제해 봅니다.

[root@centos77 docker]# cp /bin/touch /bin/touch.bak

[root@centos77 docker]# docker run -it -v /bin/:/host/ alpine rm -f /host/touch.bak

rm: can't remove '/host/touch.bak': Permission denied

[root@centos77 docker]#

 

이전과는 다르게 Permission denied가 발생한 것을 확인할 수 있습니다.

 

 

3. 도커 데몬을 실행하고 있는 호스트의 바이너리 파일이 남아있는지 확인해 봅니다.

[root@centos77 docker]# ll /bin/touch.bak

-rwxr-xr-x. 1 root root 62480 421 15:18 /bin/touch.bak

[root@centos77 docker]#

 

이전과는 다르게 잘 남아있는 것을 확인할 수 있습니다.

 

 

4. 컨테이너가 어떤 유저로서 가동되었는지도 체크해 봅니다.

[root@centos77 docker]# docker run -it -d alpine sh

78b9d029ac22f2b2c9bcde343a9017551754eb99b920538aaa1d553a6066679e

[root@centos77 docker]# docker ps -a

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

78b9d029ac22 alpine "sh" 4 seconds ago Up 3 seconds great_pascal

[root@centos77 docker]# ps aux | grep sh

100000 6397 0.0 0.0 1636 512 pts/0 Ss+ 15:26 0:00 sh

[root@centos77 docker]#

 

100000번 유저로서 프로세스가 실행되었습니다.

 

 

위와 다른 방법으로 namespace를 지정하는 방법도 있습니다.

컨테이너를 가동할 때에 --userns, --pid, --network 옵션을 이용하여 namespace를 지정할 수도 있습니다.

자세한 내용은 아래의 docker run 문서를 확인해 보시면 좋을 것 같습니다.

https://docs.docker.com/engine/reference/commandline/run/

 

docker run

Description Run a command in a new container Usage docker run [OPTIONS] IMAGE [COMMAND] [ARG...] Extended description The docker run command first creates a writeable container layer over the specified...

docs.docker.com