Linux(Centos or RHEL)/docker

컨테이너가 새로운 권한을 얻는것을 제한하기

최소양 2020. 4. 20. 20:03

해당 글은 아래의 튜토리얼을 바탕으로 작성하였습니다.

https://www.katacoda.com/courses/docker-security/no-new-privileges

 

Use No New Privileges flag to restrict additional access | Docker Security | Katacoda

Learn how to restrict applications with correct flags gaining root access. Learn Step 1, Step 2, Step 3, via free hands on training.

www.katacoda.com

 

 

배경지식으로 linux에서의 real usereffective user에 대해서 공부할 필요가 있습니다. 그리고, 실행 파일의 sticky bit도 아래의 링크에 나오므로 알아보시면 좋을것이라고 생각합니다.

http://ehpub.co.kr/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-8-1-%EC%8B%A4%EC%A0%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-idreal-user-id%EC%99%80-%EC%9C%A0%ED%9A%A8/

 

[리눅스 시스템 프로그래밍] 8.1 실제 사용자 ID(Real User ID)와 유효 사용자 ID(Effective User ID) – 언제나 휴일

리눅스 시스템은 다중 사용자가 사용할 수 있는 서버용 운영체제입니다. 따라서 사용자에 따라 사용 권한을 다르게 지정할 수 있습니다. 리눅스 시스템에서는 사용자 계정에 따라 사용자 ID를 부여하여 관리합니다. 사용자 ID는 정수 값이며 사용자를 구분하기 위한 값입니다. 리눅스 시스템은 사용자가 로긴하면 해당 사용자 계정에 대응하는 사용자 ID를 실제 사용자 ID(Real User ID)라고 부르고 프로세스가 수행할 때 권한은 유효 사용자 ID(Effecti

ehpub.co.kr

 

real user와 effective user에 대한 설명을 간단하게 하고 넘어가겠습니다.

일반적으로 real usereffective user는 같습니다.

Real user IDReal group ID란?

리눅스에서는 명령어의 실행 결과로 프로세스가 만들어 지는것이 일반적인데, 이 때 명령어를 실행시킨 user/group ID입니다.

Effective user IDEffective 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 uid0인 것은 원하는 결과입니다.

 

 

 

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 uidroot(0)인 것은 원하는 결과입니다. 하지만 real uidroot(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에 일치하는 이름을 찾지 못해서 발생하는 에러입니다.