ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 도커의 보안(Security) 알아보기
    SE General 2021. 8. 16. 12:23
    반응형

    보안은 중요하다는 점을 모두 알고 있습니다. 하지만 보안에 대해서 보통 까다롭고 복잡하고 지루하고 어렵다고 많이 느끼는데요. 그렇기에 대다수의 플랫폼에서 도커는 매우 사용이 간단한 보안 기능을 제공합니다. 또한, 그러한 디폴트는 완벽하지는 않지만 충분한 보안을 제공합니다. 

     

    이번 글에서는 도커 보안과 관련해 리눅스 환경 기준으로 중점이 되는 부분들인 리눅스 보안 기술과 도커 플랫폼의 보안 기술을 알아보겠습니다.

     

     

    • 리눅스 보안 기술
      • 네임스페이스
      • 컨트롤 그룹
      • capabilities
      • Mandatory access control
      • seccomp
    • 도커 플랫폼 보안 기술
      • 스웜모드의 보안
      • Image scanning
      • Docker Content Trust
      • Docker Secrets

    리눅스 보안 기술

     대부분의 컨테이너 플랫폼은 컨테이너 생성을 위해 네임스페이스와 cgroup을 사용합니다. 또한, 추가적으로 capabilities, SELinux의 Mandatory access control와 AppArmor, seccomp과 같은 기술을 사용하기도 하는데요. 도커 역시도 그러한 리눅스의 보안 기술을 활용하고 있습니다. 

     

    아래에서는 그러한 리눅스 보안 기술이 도커에서 어떻게 사용되고 있는지 알아보겠습니다.

     

    Container attack verctors - Image from [3]

     

    네임스페이스

    커널 네임스페이스는 컨테이너의 핵심 기능을 수행합니다. 네임스페이스는 OS를 나누어 컨테이너가 다수의 독립된 OS처럼 보이도록 합니다. 또한, 네임스페이스는 포트 충돌 없이 여러 웹서버가 같은 OS에 실행될 수 있도록 해줍니다. 그리고 공유한 설정 파일이나 라이브러리에 대한 conflict 없이 같은 OS에서 여러 앱들을 실행할 수 있도록 해줍니다. 

     

    • 같은 포트로 실행: 예로 네임스페이스는 여러 웹서버가 포트 443에서 실행될 수 있도록 해주는데, 컨테이너 각각에 연결된 네트워크 네임스페이스 안에서 그러한 부분이 가능하게 합니다. 가능한 이유는 각 네트워크 네임스페이스가 자신의 ip와  port 범위를 갖기 때문인데요. 각각을 도커 호스트의 다른 포트에 매핑해야 하지만, 각각은 다른 포트를 사용할 필요없이 실행될 수 있습니다.
    • 같은 설정 파일이나 라이브러리에 접근: 각각 마다 고유한 공유 라이브러리와 설정 파일을 요구하는 여러 앱을 실행할 수 있습니다. 그것을 위해서 각 애플리케이션은 각 mount 네임스페이스에서 실행되는데요. 이 부분은 각 mount 네임스페이스는 시스템 상에서 독립된 디렉토리 복사본을 가질 수 있기에 가능합니다.

     

    이러한 네임스페이스를 직접 다루는 것은 매우 어려운 일입니다. 도커는 그러한 복잡한 부분을 감추고 컨테이너 생성을 위해 필요한 네임스페이스를 관리해줍니다. 리눅스 상에서 도커는 아래와 같은 커널 네임스페이스를 사용합니다:

     

    • Process ID (pid)
    • Network (net)
    • Filesystem/mount (mnt)
    • Inter-process Communication (ipc)
    • User (user)
    • UTS (uts)

    중요한 부분은 도커 컨테이너는 구조화된 네임스페이스 묶음이라는 점입니다. 그 의미는 각 컨테이너에서 OS 레벨 독립성 이루고 있다는 것을 말합니다. 각 네임스페이스는 아래와 같습니다:

    Image from Author inspired by [1]

    • Process ID 네임스페이스

    도커는 pid 네임스페이스를 사용하여 각 컨테이너에 독립된 프로세스 트리를 제공합니다. 그 의미는 각 컨테이너는 자신의 PID 1을 가진다는 것입니다. PID 네임스페이스는 또한 하나의 컨테이너가 다른 컨테이너들의 프로세스 트리에 접근하지 못한다는 것을 의미합니다. 

     

    • 네트워크 네임스페이스

    도커는 net 네임스페이스를 사용해 각 컨테이너가 독립된 네트워크 스택을 가질 수 있도록 합니다. Stack에는 interfaces, IP 주소들, 포트 범위, 라우팅 테이블이 포함됩니다. 예로, 각 컨테이너는 자신의 eth0 인터페이스를 유니크한 ip와 포트 범위와 같이 가지게 됩니다. 

     

    • Mount 네임스페이스

    각 컨테이너는 유니크하고 독립된 root 파일시스템을 가집니다. 

     

    • Inter-process 커뮤니케이션 네임스페이스

    도커는 ipc 네임스페이스를 컨테이너 안에서 공유 메모리 접근을 위해 사용합니다. 또한, 컨테이너를 컨테이너 외부의 공유 메모리와 분리시킬 수 있도록 합니다. 

     

    • User 네임스페이스

    user 네임스페이스는 컨테이너 내부의 user를 리눅스 호스트에 다른 user로 매핑할 수 있도록 합니다. 

     

    • UTS 네임스페이스

    도커는 uts 네임스페이스를 통해 각 컨테이너에 고유한 hostname을 제공합니다.

     

    컨트롤 그룹

    네임스페이스가 분리(isolation)을 위한 것이라면 컨트롤 그룹(cgroups)은 제한을 설정하기 위한 것입니다.

     

    Cgroups은 컨테이너가 얼마나 자원을 쓸 수 있는지 제한을 가하여 하나의 컨테이너가 호스트의 모든 자원을 사용하는 것을 막습니다. 

     

    Capabilities

    root 권한은 모든 권한을 가지고 있기에 매우 위험합니다. 그렇기에 컨테이너를 root 권한으로 실행하는 것은 권장되지 않습니다. 하지만, 컨테이너를 권한이 없는 non-root 유저로 실행하는 것도 매우 까다로운데요. 예로, 대부분의 리눅스 시스템에서 non-root 유저는 아무런 권한이 없어서 쓸모가 없는 경우가 많습니다. 이러한 문제점을 해결하기 위해 컨테이너 실행을 위해 필요한 root 권한을 선택적으로 제공할 수 있는 capabilites를 사용하게 됩니다. 그 몇 가지 예로는:

     

    • CAP_CHOWN: 파일 오너쉽 변경
    • CAP_NET_BIND_SERVICE: 낮은 숫자의 네트워크 포트에 소켓을 바인드 할 수 있도록 함
    • CAP_SETUID: 프로세스 priviledge 레벨 올리는 것을 가능하게 함
    • CAP_SYS_BOOT: 시스템을 reboot할 수 있도록 함

    도커는 사용자가 컨테이너를 root로 실행 시 필요한 capabilities를 사용하고, 컨테이너 실행에 필요없는 capabilities는 제공되지 않습니다. 

     

    Mandatory Access Control Systems

    도커는 AppArmor와 SELinux와 같은 주요 Linux MAC 기술을 사용합니다. 

     

    리눅스 버젼에 따라, 도커는 새로운 컨테이너에 디폴트 AppArmor profile을 적용합니다. 도커 문서에 따르면, 이 디폴트 프로파일은 "넓은 애플리케이션 호환성을 제공하며 적당히 보안에 강한"데요. 

     

    또한, 도커는 적용된 policy 없이 컨테이너 시작이 가능하도록 하고, 특정 요구사항을 만족시키기 위해 커스텀한 policies도 제공합니다. 

     

    seccomp

    도커는 필터 모드에서 호스트 커널에 하나의 컨테이너가 요청할 수 있는 시스템콜 수를 제한하기 위해 seccop를 사용합니다. 도커는 다른 보안 설정과 비슷하게, 디폴트로 제한된 seccomp 설정값을 제공합니다. 그러한 seccomp 프로파일은 설정가능하며 그러한 기본 설정 없이 컨테이너가 시작될 수 있도록 할 수도 있습니다. 

     

     

    도커 플랫폼 보안 기술

    이 부분에서는 도커 플랫폼에 의해 제공되는 주요 보안 기술을 알아봅니다.

     

    스웜 모드의 보안

    도커 스웜은 여러 도커 호스트를 클러스터링하고 명시적으로 애플리케이션을 배포할 수 있도록 해줍니다. 각 스웜은 매니저와 워커로 구성됩니다. 매니저 호스트는 클러스터의 control plane으로 클러스터 설정을 관리하고 tasks를 할당합니다. 워커는 컨테이너로 애플리케이션 코드를 실행합니다. 

     

    스웜 모드는 아래와 같은 보안 기능들을 디폴트로 제공합니다:

     

    • 암호화된 노드 ID
    • mutual 인증을 위한 TLS
    • join token 보안
    • 자동 certificate 로테이션하는 CA 설정
    • 암호화된 클러스터 저장소 (config DB)
    • 암호화된 네트워크

    안전한 스웜 설정하기 

    먼저 아래와 같은 커맨드를 실행하여 매니저 노드를 시작할 수 있습니다:

     

    $ docker swarm init
    ...

     

    위의 mgr1은 스웜의 첫 매니저로 설정됨과 동시에 root certificate authority (CA) 역할을 합니다. mgr1은 스스로를 등록 시에 스웜의 매니저임을 식별하는 클라이언트 인증서로 검증되며 인증서 로테이션은 디폴트로 90일로 설정됩니다. 그리고 클러스터 설정 데이터베이스는 설정되고 암호화됩니다. 발행된 보안 토큰을 사용하여 새로운 매니저나 워커가 스웜에 join할 수 있습니다. 

    Image from Author inspired by [1]

    새로운 매니저를 추가하는 작업은 2단계로 진행됩니다. 먼저, 필요한 토큰을 추출하고 이어서 docker swarm join 커맨드를 통해 더할 수 있습니다. '매니저 조인 토큰'을 커맨드에 더해주면 해당 노드는 매니저로 스웜에 편입되게 됩니다:

     

    $ docker swarm join-token manager
    ...
    
    $ docker swarm join --token <manager-join-token> <ip-of-existing-manager>:<swarm-port>

     

    이후 아래와 같은 커맨드를 통해 스웜에 등록된 노드를 확인할 수 있습니다:

    $ docker node ls

     

    mgr2가 등록된 상태는 아래와 같습니다:

    Image from Author inspired by [1]

    워커 노드의 추가도 매니저와 유사하게 아래와 같이 진행할 수 있습니다:

     

    $ docker swarn join-token worker
    ...
    
    $ docker swarm join --token SWMTKN... 172.31.5.251:2377

     

     

    TLS와 Mutual authentication

    스웜에 조인하는 모든 매니저와 워커는 클라이언트 인증서를 발급받습니다. 이 인증서는 상호인증(mutual authentication)에 사용되는데요. 이 인증서는 노드와 노드가 포함된 스웜과 스웜에서의 노드의 역할을 식별합니다. 

     

    아래와 같은 커맨드를 통해 노드의 클라이언트 인증서를 확인할 수 있습니다:

    $ sudo openssl x509 \
        -in /var/lib/docker/swarm/certificates/swarm-node.crt \
        -text
        
    ...

     

    위 커맨드의 결과로 나온 아래와 같은 certificate 정보에서  스웜 ID, 스웜 역할, 노드 ID 정보를 찾을 수 있습니다:

    Image from Author inspired by [1]

    Cluster Store

    클러스터 저장소는 스웜의 브레인으로 모든 클러스터 설정과 상태가 저장됩니다. 또한, 클러스터 저장소는 오버레이 네트워킹, Secrets과 같은 다른 도커 기술에도 중점적인 역할을 하는데요. 그렇기 때문에, 도커의 높은 보안 기능들을 사용하기 위해서는 스웜모드를 사용하는 것이 필수적입니다. 

     

    저장소는 현재 인기있는 etcd 분산 데이터베이스에 기반하였으며 스웜에 있는 모든 매니저에 자동적으로 복제되도록 설정되어 있습니다. 또한, 디폴트로 암호화되어 있습니다. 그렇기에 상용환경에서는 백업과 리커버리 솔루션을 추가하는 것이 일반적입니다. 

     

     

    이미지 보안 스캐닝을 통해 취약점 발견하기

    이미지 스캐너는 이미지를 탐색하고 알려진 취약점을 가진 패키지를 검색합니다. 이러한 부분이 발견되면, 이미지 관리자는 빠르게 패키지를 수정하거나 버젼을 변경할 수 있습니다. 

     

    이미지 스캐닝은 훌륭하지만, 역시 한계점도 가지고 있습니다. 예로, 이미지 스캐닝은 이미지에 집중하며 네트워크, 노드, 오케스트레이터의 보안 문제를 진단하지는 않습니다. 또한, 모든 이미지 스캐너가 동일하지 않기에 일부는 binary-level 스캐닝을 하지만 일부는 이미지 컨텐츠를 건너뛰고 패키지 이름만을 확인하기도 합니다. 

     

     

    Docker Content Trust로 이미지를 검증하기

    Docker Content Trust(DCT)는 사용자가 다운로드하고 실행하는 이미지의 무결성과 퍼블리셔를 빠르고 쉽게 검증할 수 있도록 도와줍니다. 특히, DCT는 인터넷과 같이 신뢰할 수 없는 네트워크를 통해 이미지를 pull할 때 중요한데요.

     

    간단하게 말하면 DCT는 개발자가 이미지를 도커 허브나 다른 컨테이너 레지스트리에 push할 때 sign을 하고, 다른 사용자가 이미지를 pull할 때 그 sign을 통해 검증합니다. 

     

    DCT는 또한 중요한 context를 제공하는데 사용될 수도 있습니다. 예로 해당 이미지가 prod, dev와 같은 특정 환경에 사용될 수 있도록 sign되었는지 또는 이미지가 새로운 버젼으로 바뀌어 stale 되었는지 등이 그러한 context에 해당됩니다. 

     

    아래와 같은 커맨드를 통해 signed 이미지를 push할 수 있습니다:

     

    $ docker trust key generate kadencho
    ...
    
    $ docker trust signer add --key kadencho.pub kadencho kadensungbincho/dct
    ...
    
    // sign and push the image
    $ docker trust sign kadensungbincho/dct:signed
    ...
    
    $ docker trust inspect kadensungbincho/dct:signed --pretty

     

    또한, 아래 커맨드 설정을 통해 signed 이미지만 pull이 허용되도록 강제할 수 있습니다:

    $ export DOCKER_CONTENT_TRUST=1

     

    Docker Secrets

    많은 애플리케이션은 비밀번호, TLS 인증서, SSH 키 등 secrets이 필요합니다.

     

    초창기의 도커에는 이러한 secrets을 관리할 제대로된 방법이 존재하지 않았습니다. 이후 도커 1.13에서 Docker Secrets이 도커 API에 중점적으로 도입되었습니다. 

     

    내부적으로 secrets은 저장 시 암호화되어 있고, 이동 시에 암호화되며, 컨테이너 안의 인메모리 파일시스템에 마운트 되어 있으며, 명시적으로 접근이 허용된 서비스만 접근 가능하도록 최소권한만이 주어지게 됩니다. 

     

    Image from Author inspired by [1]

     

     

     

    Reference

    [1] Docker Deep Dive: Zero to Docker in a single book

    [2] Container Security, Riz Rice

    [3] https://adriancitu.com/tag/linux-namespaces/

    반응형
Kaden Sungbin Cho