컨테이너가 새로운 권한을 얻는것을 제한하기
해당 글은 아래의 튜토리얼을 바탕으로 작성하였습니다.
https://www.katacoda.com/courses/docker-security/no-new-privileges
배경지식으로 linux에서의 real user와 effective user에 대해서 공부할 필요가 있습니다. 그리고, 실행 파일의 sticky bit도 아래의 링크에 나오므로 알아보시면 좋을것이라고 생각합니다.
real user와 effective user에 대한 설명을 간단하게 하고 넘어가겠습니다.
일반적으로 real user와 effective user는 같습니다.
Real user ID와 Real group ID란?
리눅스에서는 명령어의 실행 결과로 프로세스가 만들어 지는것이 일반적인데, 이 때 명령어를 실행시킨 user/group ID입니다.
Effective user ID와 Effective group ID란?
프로세스가 작업을 수행할 때의 user/group ID입니다.
컨테이너가 새로운 권한을 얻는것을 제한하기
컨테이너에 user를 지정해서 지정한 유저로 컨테이너를 실행시키고 싶을 때가 있습니다. 이를 위해서는 컨테이너를 실행할 때(“docker run“ 명령어) ”-u“옵션을 넣어주면 됩니다. 그룹을 지정하는 것은 ”-g“ 옵션입니다.
[root@centos77 no-new-privileges]# docker run -u 1000 benhall/strace-ubuntu id
uid=1000 gid=0(root) groups=0(root)
[root@centos77 no-new-privileges]#
하지만 해당 옵션만 지정한다고 해서 컨테이너 내부에서 프로세스가 생성되었을 때에, 지정한 유저로써 프로세스가 실행될까? 그렇지는 않습니다. 컨테이너가 지정한 유저 외에 다른 권한을 얻는 것을 방지하는 것은 ”--security-opt no-new-privileges“ 옵션을 넣어줌으로써 가능합니다.
아래는 ”--security-opt no-new-privileges“ 옵션을 테스트한 것입니다.
Step 1. 컨테이너 내부에서 실행시킨 프로세스의 effective user id 확인
1. 컨테이너 내부에서 실행시킬 파일을 만들고 컴파일하고, 실행파일에 실행 권한을 추가합니다.
[root@centos77 no-new-privileges]# cat 1_testnnp.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
printf("Effective uid: %d\n", geteuid());
return 0;
}
[root@centos77 no-new-privileges]# cc 1_testnnp.c –o 1_testnnp
[root@centos77 no-new-privileges]# chmod +x 1_testnnp
2. Dockerfile을 만듭니다.
[root@centos77 no-new-privileges]# cat 1_Dockerfile
FROM benhall/strace-ubuntu:latest
ADD 1_testnnp /testnnp
RUN chmod u+s /testnnp
CMD ["/testnnp"]
[root@centos77 no-new-privileges]#
(1) ”benhall/strace-ubuntu:latest“ 이미지를 base이미지로 이용합니다.
(2) ”1_testnnp” 실행파일을 컨테이너 내부에 추가합니다.
(3) 컨테이너 내부에 추가한 실행파일의 권한에 sticky bit를 추가합니다.
(4) 실행파일을 실행 시킵니다.
3. 도커파일을 빌드합니다.
[root@centos77 no-new-privileges]# docker build -f 1_Dockerfile -t new-priv-1 .
Sending build context to Docker daemon 25.09kB
Step 1/4 : FROM benhall/strace-ubuntu:latest
---> 789c7821c0df
Step 2/4 : ADD 1_testnnp /testnnp
---> 8e91a15dd1a3
Step 3/4 : RUN chmod u+s /testnnp
---> Running in 16082fb3b2d4
Removing intermediate container 16082fb3b2d4
---> cc417223567d
Step 4/4 : CMD ["/testnnp"]
---> Running in 4f977db0af0a
Removing intermediate container 4f977db0af0a
---> 8aced986f2ad
Successfully built 8aced986f2ad
Successfully tagged new-priv-1:latest
[root@centos77 no-new-privileges]#
4. 컨테이너를 실행시킵니다.
[root@centos77 no-new-privileges]# docker run --rm -u 1000 new-priv-1
Effective uid: 0
[root@centos77 no-new-privileges]#
컨테이너 내부의 실행파일의 권한이 root(0)이고, sticky bit가 있으므로, effective uid가 0인 것은 원하는 결과입니다.
Step 2. 컨테이너 내부에서 실행시킨 프로세스의 real user id, effective user id 확인
1. 컨테이너 내부에서 실행시킬 파일을 만들고 컴파일하고, 실행 권한을 추가해 줍니다.
[root@centos77 no-new-privileges]# cat 2_setuid.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
/*secure SUID programs MUST
* *not trust any user input or environment variable!! */
char *env[]={"PATH=/bin:/usr/bin",NULL};
char prog[]="/tmp/suid.sh";
if (access(prog,X_OK)){
fprintf(stderr,"ERROR: %s not executable\n",prog);
exit(1);
}
printf("running now %s ...\n",prog);
setreuid(geteuid(),geteuid());
execle(prog,(const char*)NULL,env);
perror("suid");
return(1);
}
[root@centos77 no-new-privileges]# cc 2_setuid.c –o 2_setuid
[root@centos77 no-new-privileges]# chmod +x 2_setuid
2. 위의 실행파일에서 실행시킬 쉘 스크립트를 만들고 실행 권한을 추가해 줍니다.
[root@centos77 no-new-privileges]# cat 2_suid.sh
#!/bin/sh
#suid.sh: Print user information
echo " effective user-ID:"
id -un
echo " real user-ID:"
id -unr
echo " group ID:"
id -gn
[root@centos77 no-new-privileges]# chmod +x 2_suid.sh
3. Dockerfile을 만듭니다.
[root@centos77 no-new-privileges]# cat 2_Dockerfile
FROM benhall/strace-ubuntu:latest
ADD 2_setuid /setuid
ADD 2_suid.sh /tmp/suid.sh
RUN chmod u+s /setuid /tmp/suid.sh
CMD ["/setuid"]
[root@centos77 no-new-privileges]#
(1) ”benhall/strace-ubuntu:latest“ 이미지를 base이미지로 이용합니다.
(2) ”2_setuid” 실행파일을 컨테이너 내부에 추가합니다.
(3) ”2_suid.sh” 스크립트를 컨테이너 내부에 추가합니다.
(3) 컨테이너 내부에 추가한 파일들의 권한에 sticky bit를 추가합니다.
(4) “setuid” 실행파일을 실행 시킵니다.
4. 도커파일을 빌드합니다.
[root@centos77 no-new-privileges]# docker build -f 2_Dockerfile -t new-priv-2 .
Sending build context to Docker daemon 25.09kB
Step 1/5 : FROM benhall/strace-ubuntu:latest
---> 789c7821c0df
Step 2/5 : ADD 2_setuid /setuid
---> b2f4bebe3498
Step 3/5 : ADD 2_suid.sh /tmp/suid.sh
---> 7fd62e804e74
Step 4/5 : RUN chmod u+s /setuid /tmp/suid.sh
---> Running in ad36f31fc2b4
Removing intermediate container ad36f31fc2b4
---> 75db6be10420
Step 5/5 : CMD ["/setuid"]
---> Running in 1fe5fc7ae63f
Removing intermediate container 1fe5fc7ae63f
---> cac500c9d92f
Successfully built cac500c9d92f
Successfully tagged new-priv-2:latest
[root@centos77 no-new-privileges]#
4. 컨테이너를 실행시킵니다.
[root@centos77 no-new-privileges]# docker run --rm -u 1000 new-priv-2
effective user-ID:
root
real user-ID:
root
group ID:
root
[root@centos77 no-new-privileges]#
컨테이너 내부의 실행파일의 권한이 root(0)이고, sticky bit가 있으므로, effective uid가 root(0)인 것은 원하는 결과입니다. 하지만 real uid가 root(0)인 것은 원하는 결과가 아닙니다. 컨테이너에서 프로세스가 1000번 유저가 아니라, root권한으로 파일이 실행된 것입니다!
Step 3. 컨테이너가 추가로 권한을 얻지 못하게 제한하기.
1. 앞에서 만든 “new-priv-1” 이미지를 “--security-opt no-new-privileges” 옵션을 넣어서 추가 권한을 얻지 못하게 해서 기동합니다.
[root@centos77 no-new-privileges]# docker run -u 1000 --rm --security-opt no-new-privileges new-priv-1
Effective uid: 1000
[root@centos77 no-new-privileges]#
effective uid가 1000으로 바뀌었습니다.
2. 앞에서 만든 “new-priv-2” 이미지를 “--security-opt no-new-privileges” 옵션을 넣어서 추가 권한을 얻지 못하게 해서 기동합니다.
[root@centos77 no-new-privileges]# docker run -u 1000 --rm --security-opt no-new-privileges new-priv-2
effective user-ID:
1000
id: cannot find name for user ID 1000
real user-ID:
1000
id: cannot find name for user ID 1000
group ID:
root
[root@centos77 no-new-privileges]#
effective uid와 real uid가 1000으로 바뀌었습니다. 즉 실행파일을 1000번 유저로써 실행한 것입니다.
“id: cannot find name for user ID 1000“ 는 uid에 일치하는 이름을 찾지 못해서 발생하는 에러입니다.