본문 바로가기
System/Container

Podman Rootless Container 의 PID/User Namespace

PID Namespaces 

PID Namespace는 PID에 대한 격리를 제공하는 Namespace로 자신의 PID 네임스페이스 내의 다른 프로세스만 볼 수 있고, 상위 혹은 다른 PID Namespace 내의 프로세스 정보는 확인할 수 없다.

즉, Process 에 대한 격리를 위하여 사용된다. 기능 자체가 Kernel 단에서 처리되는 것이기 때문에 100% 안전할 수 있다고 없지만 Container에서 Process 격리를 위해 선택되고 있다.

만약 좀 더 보안을 강화 한다면 SandBox 형태의 kata-container에 대한  부분도 고려하는 것이 좋아  보인다.

아래와 같이 lsns 명령을 이용하여 Namespace  사용 중인 리스트를 출력할 수 있고, "-t pid" 옵션을 추가하여, PID Namespace를 확인할 수 있다. 

부팅해서 Systemd Init 프로세스가 PID 1번으로 PID Namspace 4026531836으로 동작 중인 것을 알 수 있다.

그리고 Process의 개수는 145 개이다. 

[root@cy03-r9-031 ~]# lsns
        NS TYPE   NPROCS   PID USER   COMMAND
4026531834 time      145     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531835 cgroup    145     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531836 pid       145     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531837 user      144     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531838 uts       142     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531839 ipc       145     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531840 net       145     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531841 mnt       133     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531862 mnt         1    36 root   kdevtmpfs
4026532136 mnt         1   641 root   /usr/lib/systemd/systemd-udevd
4026532137 uts         1   641 root   /usr/lib/systemd/systemd-udevd
4026532301 mnt         2   720 root   /sbin/auditd
4026532302 mnt         2   748 dbus   /usr/bin/dbus-broker-launch --scope system --audit
4026532303 mnt         1   760 chrony /usr/sbin/chronyd -F 2
4026532304 mnt         1   753 root   /usr/sbin/irqbalance --foreground
4026532305 uts         1   760 chrony /usr/sbin/chronyd -F 2
4026532306 mnt         1   758 root   /usr/lib/systemd/systemd-logind
4026532307 uts         1   758 root   /usr/lib/systemd/systemd-logind
4026532308 mnt         1   776 root   /usr/sbin/NetworkManager --no-daemon
4026532310 user        1 30842 rocky  catatonit -P
4026532311 mnt         1 30842 rocky  catatonit -P
4026532371 mnt         1   896 root   /usr/sbin/rsyslogd -n
[root@cy03-r9-031 ~]# lsns -t pid
        NS TYPE NPROCS PID USER COMMAND
4026531836 pid     145   1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31

 

unshare 명령을 이용하여 Namepace를 만들어 본다. "–pid" 옵션을 추가하여 PID Namespace로 , -"-fork " 옵션을 추가 하여 Fork 하고, "--mount-proc /bin/bash" 옵션을 추가 하여 Bash Shell을 첫 번째 Process로 마운트 한다. 

그래서 "/bin/bash"를 PID 1로 하는 새로운 PID Namespace를 확인할 수 있다. 단순 구분을 위하여 PS1 설정하여 CHILD1로 구분하고자 하였다. 

sleep 명령을 주고 임의 Process를 구동했고 해당 PID는 19로 확인된다. ps 명령으로 예상한 것과 같이 기존 부모 PID Namepsce에서 확인된 다른 Process는 확인할 수 없다. 

[root@cy03-r9-031 ~]# ps -ef |  head -n 5
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 Feb14 ?        00:00:36 /usr/lib/systemd/systemd --switched-root --system --deserialize 31
root           2       0  0 Feb14 ?        00:00:00 [kthreadd]
root           3       2  0 Feb14 ?        00:00:00 [rcu_gp]
root           4       2  0 Feb14 ?        00:00:00 [rcu_par_gp]
[root@cy03-r9-031 ~]# unshare --pid --fork --mount-proc /bin/bash
[root@cy03-r9-031 ~]#  PS1="CHILD1"
CHILD1# sleep 11111&
[1] 19
CHILD1# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 02:39 pts/0    00:00:00 /bin/bash
root          19       1  0 02:45 pts/0    00:00:00 sleep 11111
root          20       1  0 02:45 pts/0    00:00:00 ps -ef
 
CHILD1# lsns
        NS TYPE   NPROCS PID USER COMMAND
4026531834 time        3   1 root /bin/bash
4026531835 cgroup      3   1 root /bin/bash
4026531837 user        3   1 root /bin/bash
4026531838 uts         3   1 root /bin/bash
4026531839 ipc         3   1 root /bin/bash
4026531840 net         3   1 root /bin/bash
4026532312 mnt         3   1 root /bin/bash
4026532313 pid         3   1 root /bin/bash
CHILD1# lsns  -t pid
        NS TYPE NPROCS PID USER COMMAND
4026532313 pid       3   1 root /bin/bash

다른 프롬프트에서 다시 PID Namespace를 확인하면, 생성한 PID Namespace 정보를 확인할 수 있다. "4026532313"라는 새로운 번호와 함께 생성되었다. 

여기서 PID는 1이 아닌 178200으로 확인된다.  "/proc/[PID번호]/status"에서 NSpid 필드는 현재 사용하는 Namespace에서 해당 Process 의 PID 정보와 함께 하위 Namespace 에서 사용 중인 PID 정보를 를 확인할 수 있다. 

그래서 "NSpid: 178200 1" 정보를 보고 첫 번째 필드인 현재 PID Namespace 에는 178200이고 하위에서는 1번을 갖는 것을 알 수 있다. 

root@cy03-r9-031 ~]# lsns -t pid
        NS TYPE NPROCS    PID USER COMMAND
4026531836 pid     144      1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026532313 pid       1 178200 root /bin/bash
 
[root@cy03-r9-031 ~]# cat /proc/178200/status | grep -i nspid
NSpid: 178200 1

이어서 sleep명령을 준 것도 동일하게 확인할 수 있다.

[root@cy03-r9-031 ~]# ps -ef | grep sleep
root      179823  178200  0 02:45 pts/0    00:00:00 sleep 11111
 
[root@cy03-r9-031 ~]#  cat /proc/179823/status | grep -i nspid
NSpid:  179823  19

하지만 하위 Namespace에서는 확인할 수 없다. 상위 Process를 확인 못하는 것과 동일하다. 

CHILD1# cat /proc/19/status  | grep -i nspid
NSpid:  19

이번에는 CHILD1 환경에서 다시 한번 PID Namespace를 만들어 본다. CHILD1-1으로 지정하고, sleep을 동일하게 주고 PID 가 17 번인 것을 확인한다. 

CHILD1# unshare --fork --pid --mount-proc /bin/bash
CHILD1-1PS1="CHILD1-1# "
CHILD1-1# sleep 22222&
[1] 17
CHILD1-1# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 02:57 pts/0    00:00:00 /bin/bash
root          17       1  0 02:57 pts/0    00:00:00 sleep 22222
root          18       1  0 02:57 pts/0    00:00:00 ps -ef
 
CHILD1-1# cat /proc/17/status | grep -i nspid
NSpid:  17

 

이미 CHILD1에서 CHILD1-1로 한번 들어갔기 때문에 외부 다른 쉡에서 CHILD1의 PID Namespace로 진입하기 위해서 netenter 명령으로 이용한다. 

netenter는 Namespace에 상호 작용을 위하여 타입에 맞춰서 사용이 가능한데 PID를 지정하여 진입할 수 있다. 진입 후에 동일하게 CHILD1-1을 하위로 갖고 있으니 CHILD1-1에서 sleep 명령을 한 Process를 확인할 수 있으며, NSpid 도 하위에서 17번의 PID를 갖고 현재 PID Namespace에서 48을 소유하고 있는 것을 알 수 있다. 

당연하겠지만 상위 Root가 되는 PID Namespace정보는 알 수 없는 상태이다. 

[root@cy03-r9-031 ~]# lsns -t pid
        NS TYPE NPROCS    PID USER COMMAND
4026531836 pid     145      1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026532313 pid       3 178200 root /bin/bash
4026532315 pid       2 182878 root /bin/bash
 
[root@cy03-r9-031 ~]#  nsenter  --pid --target 178200  --mount
[root@cy03-r9-031 /]# ls
afs  bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@cy03-r9-031 /]# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 02:39 pts/0    00:00:00 /bin/bash
root          19       1  0 02:45 pts/0    00:00:00 sleep 11111
root          31       1  0 02:57 pts/0    00:00:00 unshare --fork --pid --mount-proc /bin/bash
root          32      31  0 02:57 pts/0    00:00:00 /bin/bash
root          48      32  0 02:57 pts/0    00:00:00 sleep 22222
root          53       0  0 03:02 pts/4    00:00:00 -bash
root          72      53  0 03:02 pts/4    00:00:00 nsenter --pid --target 178200 --mount
root          73      72  0 03:02 pts/4    00:00:00 -bash
root          92      73  0 03:02 pts/4    00:00:00 ps -ef
[root@cy03-r9-031 /]# PS1="CHILD1#"
CHILD1#lsns  -t pid
        NS TYPE NPROCS PID USER COMMAND
4026532313 pid       7   1 root /bin/bash
4026532315 pid       2  32 root /bin/bash
CHILD1#ps -ef | grep slee
root          19       1  0 02:45 pts/0    00:00:00 sleep 11111
root          48      32  0 02:57 pts/0    00:00:00 sleep 22222
root          96      73  0 03:03 pts/4    00:00:00 grep --color=auto slee
CHILD1#cat /proc/48/status | grep -i nspid
NSpid:  48      17

다시 처음 Root PID Namepsace에서 확인하면 연속적으로 "183058 48 17" 값을 NSpid로 확인할 수 있다. 

[root@cy03-r9-031 ~]# lsns -t pid
        NS TYPE NPROCS    PID USER COMMAND
4026531836 pid     145      1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026532313 pid       3 178200 root /bin/bash
4026532315 pid       2 182878 root /bin/bash
[root@cy03-r9-031 ~]# ps -ef| grep sleep
root      179823  178200  0 02:45 pts/0    00:00:00 sleep 11111
root      183058  182878  0 02:57 pts/0    00:00:00 sleep 22222
root      183357   45946  0 02:58 pts/4    00:00:00 grep --color=auto sleep
[root@cy03-r9-031 ~]# cat /proc/183058/status | grep nspid
[root@cy03-r9-031 ~]# cat /proc/183058/status | grep -i nspid
NSpid:  183058  48      17

최종적으로 아래 그림과 같은 부모/자식 관계의 격리된 Process와 PID들을 확인할 수 있다.

Nesting PID namespace 구조 형태를 갖는 Child1-1 Namespaces는 상위에 Child1 PID namespace를 부모로 다시 Host OS 의 PID namespace부모를 갖는 형태로 트리 자료구조 형태를 띤다.

 

다시 Podman으로 sleep을 실행하면 위 격리와 같은 동일한 형태로 Process 가 격리 되는 것을 확인할 수 있다. 

[root@cy03-r9-031 ~]#   podman run -d --name child2 ubuntu sleep infinity
cc23be855f27eb28d3347b25118061b317a825a8d3ccd6188f832569e667153b
 
[root@cy03-r9-031 ~]# podman exec -it child2 bash
root@cc23be855f27:/# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 04:30 ?        00:00:00 sleep infinity
root@cc23be855f27:/# cat /proc/1/status | grep -i nspid
NSpid:  1
root@cc23be855f27:/# exit
exit
 
[root@cy03-r9-031 ~]# lsns -t pid
        NS TYPE NPROCS    PID USER COMMAND
4026531836 pid     150      1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026532313 pid       6 178200 root /bin/bash
4026532315 pid       2 182878 root /bin/bash
4026532387 pid       1 208144 root sleep infinity
 
[root@cy03-r9-031 ~]# cat /proc/208144/status | grep -i nspid
NSpid:  208144  1
 
## CleanUp Container
[root@cy03-r9-031 ~]# kill -9 208144
[root@cy03-r9-031 ~]# podman stop child2
child2
[root@cy03-r9-031 ~]# podman rm --force   child2
child2

만약 이러한 격리를 무시하고 Host OS와 동일한 선에서 구성하기 위해서는 "–pid=host"옵션을 추가하면 된다. 이럴 경우 아래와 같이 Host OS에 있는 Process 동일한 형태의 PID와 PID Namespace에서 실행된다.

이런 경우 격리가 안되기 때문에 보안 해당 방법으로 Conrtainer 구성하는 것은 위험하다. 이는 기본 Drop 되어 있는 Capability 중 "CAP_SYS_ADMIN"의 해제만큼이나 위험하다.

[root@cy03-r9-031 ~]#   podman run -d --pid=host --name child3 ubuntu sleep infinity
e67862837b4ee809c79edb0c7297e71cb456446957f5e716d94806aeb9926dc7
 
[root@cy03-r9-031 ~]#   podman run -d --pid=host --name child3 ubuntu sleep infinity
e67862837b4ee809c79edb0c7297e71cb456446957f5e716d94806aeb9926dc7
[root@cy03-r9-031 ~]#  podman exec -it child3 bash
root@e67862837b4e:/# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 Feb14 ?        00:00:46 /usr/lib/systemd/systemd --switched-root --system --deserialize 31
root           2       0  0 Feb14 ?        00:00:00 [kthreadd]
root           3       2  0 Feb14 ?        00:00:00 [rcu_gp]
root           4       2  0 Feb14 ?        00:00:00 [rcu_par_gp]
//....
root@e67862837b4e:/# exit
exit
[root@cy03-r9-031 ~]# ps -ef | grep sleep
root      179823  178200  0 02:45 pts/0    00:00:00 sleep 11111
root      183058  182878  0 02:57 pts/0    00:00:00 sleep 22222
root      210127  210125  0 04:36 ?        00:00:00 sleep infinity
[root@cy03-r9-031 ~]# cat /proc/210127/status | grep -i nspid
NSpid:  210127
 
 ## CleanUp Container
[root@cy03-r9-031 ~]# kill -9 210127
[root@cy03-r9-031 ~]# podman rm --force child3
child3

이는 일반 사용자 계정으로 Podman을 실행하는 Rootless Container에서도 동일하게 작동한다. 그래도, Rootless로 일반 사용자이기 때문에 Root 권한을 갖는 실행에 대한 영향도가 큰 이슈를 막을 수 있다. 

그렇기 때문에, Rootless는 Container 구성에 있어서 보안을 고려한다면 매우 필수 적인 요소이다.  

[rocky@cy03-r9-031 ~]$ id
uid=1000(rocky) gid=1000(rocky) groups=1000(rocky),4(adm),190(systemd-journal) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[rocky@cy03-r9-031 ~]$ podman run -d --pid=host --user sync --name child4 ubuntu sleep infinity
da6999ca2a007da510436abc17cc32d30aeb86bbbed5e4457f1788ad73e03ec5
 
[rocky@cy03-r9-031 ~]$ ps -ef | grep sleep
root      179823  178200  0 02:45 pts/0    00:00:00 sleep 11111
root      183058  182878  0 02:57 pts/0    00:00:00 sleep 22222
100003    212944  212942  0 04:46 ?        00:00:00 sleep infinity
rocky     213307    2202  0 04:47 pts/1    00:00:00 grep --color=auto sleep
 
 [rocky@cy03-r9-031 ~]$ lsns  -t pid
        NS TYPE NPROCS   PID USER  COMMAND
4026531836 pid      12  2181 rocky /usr/lib/systemd/systemd --user

Rootless Container User Namesace 

앞서 PID Namespace를 확인하면서 , 권한 상승에 따른 이슈가 발생하기에, Rootless를 통한 취약성의 완화를 이야기했다. Rootless Container는 일반적으로 Root로 실행되는 Container와 다르게 고려할 부분이 많다.

그래서 RootLess Container Project 가 별도로 진행되어 (https://rootlesscontaine.rs/) Podman을 비롯한 Docker , Lxc 등 다양하게 활용되고 있다. 

이러한 Rootless Container의 근본적인  철학은 프로젝트 처음 문서에 가장 잘 설명되어 있다. 내부의 Container 계정이 모든 실행 /도구 설치 등을 할 수 없는 것이다. 

 

"Rootless containers refers to the ability for an unprivileged user to create, run and otherwise manage containers. This term also includes the variety of tooling around containers that can also be run as an unprivileged user.
“Unprivileged user” in this context refers to a user who does not have any administrative rights, and is “not in the good graces of the administrator” (in other words, they do not have the ability to ask for more privileges to be granted to them, or for software packages to be installed).
=> 루트리스 컨테이너는 일반 사용자가 컨테이너를 생성, 실행 및 관리할 수 있는 기능을 말합니다. 이 용어는 또한 일반 사용자가 실행할 수 있는 컨테이너와 관련된 다양한 도구들도 포함합니다.
=> 이 맥락에서 '일반 사용자'란 관리자 권한이 없는 사용자를 의미하며, 관리자의 허가를 받지 않아 추가적인 권한을 부여받거나 소프트웨어 패키지를 설치할 수 없습니다."

#그래서 "docker run --user somebody" 와 내부 프로세스는 root 이지만 실제 Host OS 에서는 
#Root 로 동작 하는 경우나, docker.sock 에 대한 권한이 일반 사용자가 있어서 해당 docker.sock 을 통하여 설정 하는 경우 등은 Rootless 로 간주 하지 않는다.


root@cyyoon-c1-test-064:~# docker run --user sync ubuntu sleep 1000
//...
root@cyyoon-c1-test-064:~# ps -ef | grep sleep
root     1436472 1434966  0 06:27 pts/3    00:00:00 docker run --user sync ubuntu sleep 1000 ## <---실제로는 Root 계정으로 실행

 

즉 여기서 이야기하는 부분은 User와 Group에 관련된 부분이며, 이 부분의 Namespace에 해당 하는 User Namespace 에 대한 부분이 중점이 된다.

그러면 Case별로 UID 가 어떤 식으로 Host OS노출되는지 확인한다.

 

1) Root 계정으로 Root Container 실행 

Root 환경에서 기본적으로 Root로 실행되는 Container를 실행하고 Host OS에서 Process의 UID를 확인하면 동일한 UID로 확인된다.

[root@cy03-r9-031 ~]# podman run  -d --name hostroot_containerroot ubuntu:22.04 tail -f /dev/null
[root@cy03-r9-031 ~]# podman exec -it  hostroot_containerroot bash
root@3a2dc76f6251:/# id
uid=0(root) gid=0(root) groups=0(root)
root@3a2dc76f6251:/# whoami
root
root@3a2dc76f6251:/# sleep 1000 &
[1] 7
root@3a2dc76f6251:/# ps -ef n | grep sleep
       0       7       1  0 06:35 ?        S      0:00 sleep 1000
root@3a2dc76f6251:/# exit
exit
[root@cy03-r9-031 ~]# ps -ef n | grep sleep
       0   34689   34466  0 06:35 ?        S      0:00 sleep 1000 ### <------------- Root(uid 0) 실행

2) Root 계정으로 User계정(sync) Container 실행 

Root 환경에서 User 계정으로 실행하는 Container의 경우 해당 Container 내부의 UID와 Host OS UID 가 동일 하게 확인된다. 

[root@cy03-r9-031 ~]# podman run  -d --name  hostroot_containerrootless --user sync  ubuntu:22.04 tail -f /dev/null
[root@cy03-r9-031 ~]# podman exec -it hostroot_containerrootless bash
sync@79c7f8c5493e:/$ id
uid=4(sync) gid=65534(nogroup) groups=65534(nogroup)
sync@79c7f8c5493e:/$ whoami
sync
sync@79c7f8c5493e:/$ sleep 2000 &
[1] 6
sync@79c7f8c5493e:/$ ps -ef n | grep sleep
       4       6       1  0 06:41 ?        S      0:00 sleep 2000
 
 sync@79c7f8c5493e:/$ exit
exit
[root@cy03-r9-031 ~]# ps -ef n  | grep sleep
       0   34689   34466  0 06:35 ?        S      0:00 sleep 1000
       4   38014   37832  0 06:41 ?        S      0:00 sleep 2000 ### <------------- User sync (uid 4) 실행

3) User계정(rocky)으로 Root계정 Container 실행 

User계정 환경에서 Root 계정으로 실행하는 Container의 경우 해당 Container 내부의 UID는 Root UID 인 0으로 나오지만,  Host OS UID 실제 Container를 실행 한 User의 UID로  확인된다. 

[rocky@cy03-r9-031 ~]$ whoami
rocky
[rocky@cy03-r9-031 ~]$ id
uid=1000(rocky) gid=1000(rocky) groups=1000(rocky),4(adm),190(systemd-journal) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[rocky@cy03-r9-031 ~]$ podman run -d --name hostuser_containerroot ubuntu:22.04 tail -f /dev/null
5e0f5a40615f8795bfe1ed9b4bb4a0d328e25ee18b71b5155fd3bf2808a21ab6
 
[rocky@cy03-r9-031 ~]$ podman exec -it hostuser_containerroot bash
root@5e0f5a40615f:/# id
uid=0(root) gid=0(root) groups=0(root)
root@5e0f5a40615f:/# whoami
root
root@5e0f5a40615f:/# sleep 3000 &
[1] 7
root@5e0f5a40615f:/# ps -ef n | grep sleep
       0       7       2  0 06:55 pts/0    S      0:00 sleep 3000
       0       9       2  0 06:55 pts/0    S+     0:00 grep --color=auto sleep
[rocky@cy03-r9-031 ~]$ exit
[rocky@cy03-r9-031 ~]$ ps -ef n | grep sleep
       4   38014   37832  0 06:41 ?        S      0:00 sleep 2000
    1000   45267   44908  0 06:55 ?        S      0:00 sleep 3000   ### <------------- User rocky (uid 1000) 실행
       0   46129   46056  0 06:56 ?        S      0:00 sleep 1000

4) User계정(rocky)으로  User계정(sync) Container 실행 

User계정 환경에서 User계정 실행하는 Container의 경우 해당 Container 내부의 UID는 해당 User의 UID 인 4로 나오지만,  Host OS UID 는 100003 이는 숫자가 나왔다. 

[rocky@cy03-r9-031 ~]$ whoami
rocky
[rocky@cy03-r9-031 ~]$ id
uid=1000(rocky) gid=1000(rocky) groups=1000(rocky),4(adm),190(systemd-journal) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[rocky@cy03-r9-031 ~]$ podman run -d --user sync  --name hostuser_containeruser ubuntu:22.04 tail -f /dev/null
9139441dae1fc1e5c0972f7965e425aa0e481c97f9b0f2373c829fca97b3fb01
[rocky@cy03-r9-031 ~]$ podman exec -it hostuser_containeruser bash
sync@9139441dae1f:/$ whoami
sync
sync@9139441dae1f:/$ id
uid=4(sync) gid=65534(nogroup) groups=65534(nogroup)
sync@9139441dae1f:/$ sleep 4000 &
[1] 6
sync@9139441dae1f:/$ ps -ef n | grep sle
       4       6       1  0 06:57 ?        S      0:00 sleep 4000
sync@9139441dae1f:/$ exit
exit
[rocky@cy03-r9-031 ~]$ ps -ef n| grep  sleep
       4   38014   37832  0 06:41 ?        S      0:00 sleep 2000
    1000   45267   44908  0 06:55 ?        S      0:00 sleep 3000
       0   46129   46056  0 06:56 ?        S      0:00 sleep 1000
  100003   46871   46656  0 06:57 ?        S      0:00 sleep 4000   ### <------------uid  100003 실행


일반 계정으로 실행한 결과에서 Root로 실행하여도 결국 해당 일반 계정으로 Host OS로 매핑되어 실행되며, 일반 계정에서 다시 일반 계정으로 실행하면 10003이라는 숫자를 확인할 수 있다. 

우선 , 일반 계정으로 Rootless로 실행했을 때 HostOS의 Root로 실행이 안된다는 것을 알았다. 

그러면 User Namespace를 확인해 본다. 위에 테스트는 모두 종료하고 일반 계정 (Rocky)로 다시 Root로 실행되는 Container를 실행한다. 

Container 내부에서 User를 생성하고, 해당 User로 스위칭 후 Sleep Process를 실행해서 User Namspace를 보면  4026532309라는 것을 알 수 있다. 

[rocky@cy03-r9-031 ~]$ podman run -d   --name hostuser_containerroot ubuntu:22.04 tail -f /dev/null
 
root@50199e2b545d:/# useradd -u 2000 user1
root@50199e2b545d:/# su - user1
 
$ sleep 30000&
 
$ ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 07:43 ?        00:00:00 tail -f /dev/null
root           2       0  0 07:43 pts/0    00:00:00 bash
root          14       2  0 07:43 pts/0    00:00:00 su - user1
user1         15      14  0 07:43 pts/0    00:00:00 -sh
user1         19      15  0 07:43 pts/0    00:00:00 sleep 30000
user1         20      15  0 07:44 pts/0    00:00:00 ps -ef
 
$ lsns -t user
        NS TYPE  NPROCS PID USER  COMMAND
4026532309 user       3  19 user1 sleep 30000

다시 Host OS에서 확인 User Namespace를 확인하면 일반 계정 (Rocky)로 실행된 User Namespace와 동일하고, Sleep명령을 Process로 직접 확인 후 해당 PID를 확인해도 Host OS의 User Namespace가 아닌 것으로 확인된다.

구조를 보면 앞서 다뤘던 PID Namespace와 동일하게 부모 자식 간에 중복되는 UID를 부여하고, Host OS에서 구분이 된다. 

그리고, 현 테스트에서 UID를 다시 확인하면 Container 내부에서 User 생성 시 2000으로 지정하여, 확인했으나 Host OS에서는 10199 라는 숫자가 나왔다. 

[root@cy03-r9-031 ~]# lsns -t user
        NS TYPE  NPROCS   PID USER  COMMAND
4026531837 user     146     1 root  /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026532309 user       8  2235 rocky catatonit -P
 
[root@cy03-r9-031 ~]# ps -ef | grep sleep
101999     72703   72157  0 07:43 pts/0    00:00:00 sleep 30000   ##<<<---------UID 10199   
[root@cy03-r9-031 ~]# lsns -p 72703 -t user
        NS TYPE  NPROCS   PID USER  COMMAND
4026532309 user       8  2235 rocky catatonit -P

이 숫자는 Host OS 에서 매핑하기 위하여 별도의 계정 별 Map 이 존재하며, Container 내부에서는 uid_map 파일에서 그 숫자를 확인할 수 있다. 

즉 , Container 내부에서  Root는 1000번으로, 2000번 UID를 갖는 경우 범위 기반으로 아래와 같은 계산 공식을 나타낸다.

호스트 UID - 매핑 시작 호스트 UID + 네임스페이스 내 매핑 시작 UID= 내부 UID 

 

그렇기 때문에  101999 - 100000 + 1 = 2000이라는 값이 나와서 10999로 확인 된다. 

$ id
uid=2000(user1) gid=2000(user1) groups=2000(user1)
$ cat /proc/self/uid_map
         0       1000          1
         1     100000      65536

이 숫자는 Host OS의 subuid파일에서 관리되며, User 생성할 때마다 추가된다. 그리고 GID 또한 동일하게 subgid 파일로 관리된다. 

[root@cy03-r9-031 ~]# cat /etc/subuid
rocky:100000:65536
cyyoon:165536:65536
[root@cy03-r9-031 ~]# useradd addsub1
[root@cy03-r9-031 ~]# cat /etc/subuid
rocky:100000:65536
cyyoon:165536:65536
addsub1:231072:65536
[root@cy03-r9-031 ~]# useradd addsub2
[root@cy03-r9-031 ~]# cat /etc/subuid
rocky:100000:65536
cyyoon:165536:65536
addsub1:231072:65536
addsub2:296608:65536
 
 
[root@cy03-r9-031 ~]# cat /etc/subgid
rocky:100000:65536
cyyoon:165536:65536
addsub1:231072:65536
addsub2:296608:65536

물론 Podman에서 이러한 User Namespace를 제어하는 옵션이 존재한다. Rootless Container로 별도의 --user 옵션이 없다면 root로 내부에서 실행되는 것을 확인했었다.

"--userns=keep-id" 옵션을 추가하면 아래와 같이 현재 User Namepsace를 유지한 상태로 Container를 실행하게 된다.

[rocky@cy03-r9-031 ~]$ id
uid=1000(rocky) gid=1000(rocky) groups=1000(rocky),4(adm),190(systemd-journal) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[rocky@cy03-r9-031 ~]$ podman run -d --userns=keep-id  --name test2 ubuntu:22.04 tail -f /dev/null
9182dc6efc52dfebfc14b1a0452354eed202aada5ebd39f6367b38c6fc7513c4
rocky@9182dc6efc52:/$ whoami
rocky
[rocky@cy03-r9-031 ~]$ podman exec -it test2 bash
rocky@9182dc6efc52:/$ id
uid=1000(rocky) gid=1000(rocky) groups=1000(rocky)

이러한 매핑을 진행하는 바이너리는 newuidmap와 newugdmap로. SetUID 설정이 반드시 되어야 한다. 

[root@cy03-r9-031 ~]# ls -al /usr/bin/newuidmap
-rwxr-xr-x. 1 root root 38840 Oct 30 19:56 /usr/bin/newuidmap
 
[root@cy03-r9-031 ~]#   /usr/bin/newuidmap --help
usage: newuidmap <pid> <uid> <loweruid> <count> [ <uid> <loweruid> <count> ] ...
 
[root@cy03-r9-031 ~]# ls -al /usr/bin/newgidmap
-rwxr-xr-x. 1 root root 42960 Oct 30 19:56 /usr/bin/newgidmap

 

 

 


Reference

PID Namespace  테스트 레드햇 영상:https://youtu.be/J17 rXQ5 XkDE? si=tjxFdkEacO_MzYns

User Namespace  테스트 레드햇 영상: https://www.redhat.com/sysadmin/behind-scenes-podman

PID Namespace 분석 글: https://linuxias.github.io/container/namespace/2_pid_namespace/

 

 

 

 

 

 

 

 

반응형