namespace를 이용하여 컨테이너들을 고립시켜보자
해당 글을 아래의 튜토리얼을 테스트 해 본 것입니다.
https://www.katacoda.com/courses/docker-security/userns-user-namespaces
배경지식 linux에서의 namespace란?
리눅스에서 namespace는 lightweight 가상화 솔루션이다. XEN이나 KVM 같은 가상화 솔루션들은 커널 인스턴스들을 생성하여 동작시키는 것에 반하여 리눅스의 namespace는 커널 인스턴스를 만들지 않고 기존의 리소스들을 필요한 만큼의 namespace로 분리하여 묶어 관리하는 방법으로 사용한다.
자세한 내용은 아래를 확인해 주세요.
http://jake.dothome.co.kr/namespace/
기본값에서는 도커 컨테이너는 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 4월 21 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
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 ~]#
uid와 gid가 1000인 것을 확인할 수 있습니다.
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 ~]#
이 지점에서 가끔 오류가 날 수도 있습니다. 그런데 오류가 어떤 것 때문에 나는지 정확하게 알려주지 않는 경우가 있습니다...
------
4월 21 14:07:44 centos77.linux systemd[1]: docker.service: main process exited, code=exited, status=1/FAILURE
4월 21 14:07:44 centos77.linux systemd[1]: Failed to start Docker Application Container Engine.
4월 21 14:07:44 centos77.linux systemd[1]: Unit docker.service entered failed state.
4월 21 14:07:44 centos77.linux systemd[1]: docker.service failed.
4월 21 14:07:46 centos77.linux systemd[1]: docker.service holdoff time over, scheduling restart.
4월 21 14:07:46 centos77.linux systemd[1]: Stopped Docker Application Container Engine.
4월 21 14:07:46 centos77.linux systemd[1]: Starting Docker Application Container Engine...
------
이건 또 무슨 에러인가..
이래저래 찾아서 테스트를 해본 결과, “daemon.json”파일에서 지정한 "userns-remap" 옵션의 "1000:1000" 값에 해당되는 유저를 “/etc/subuid“와 ”/etc/subgid“에서 찾지 못하는 경우에 도커 데몬의 재시작이 에러가 발생하는 것을 알 수 있었습니다.
아래와 같이 설정들을 확인해 봅니다.
1] 1000번 uid와 gid가 있는가
[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/
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 4월 21 14:19 100000.100000
drwx------. 2 root root 24 4월 20 13:45 builder
drwx--x--x. 4 root root 92 4월 20 13:45 buildkit
drwx------. 3 root root 78 4월 21 13:36 containers
drwx------. 3 root root 22 4월 20 13:45 image
drwxr-x---. 3 root root 19 4월 20 13:45 network
drwx------. 13 root root 4096 4월 21 13:56 overlay2
drwx------. 4 root root 32 4월 20 13:45 plugins
drwx------. 2 root root 6 4월 21 13:56 runtimes
drwx------. 2 root root 6 4월 20 13:45 swarm
drwx------. 2 root root 6 4월 21 13:56 tmp
drwx------. 2 root root 6 4월 20 13:45 trust
drwx------. 2 root root 25 4월 20 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
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 4월 21 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/