Capabilities 활용 가이드

지난 주, 새로운 마이크로서비스 아키텍처로 전환하는 프로젝트를 진행하던 중이었습니다. 모든 서비스를 Docker 컨테이너로 구성하고, NGINX 컨테이너를 통해 외부 요청을 각 서비스에 라우팅하는 구조였죠. 그런데 개발 환경에서는 문제없이 작동하던 시스템이 실제 운영 환경에 배포하는 과정에서 예상치 못한 오류를 뱉어냈습니다.

Error starting userland proxy: listen tcp 0.0.0.0:80: bind: permission denied

보안 정책상 컨테이너는 root가 아닌 일반 사용자 권한으로 실행해야 했는데, 이로 인해 80, 443 포트에 접근할 수 없었던 것입니다. 서비스 중단 없이 이 문제를 해결해야 했고, 여기서 Linux Capabilities 시스템의 강력함을 경험하게 되었습니다.

Linux Capabilities란?

전통적인 Unix/Linux 시스템에서는 권한 관리가 ‘root vs non-root’의 이분법적 구조였습니다. 그러나 이는 “모든 것이 가능하거나 아무것도 할 수 없거나”의 극단적인 상황을 만들어냈죠. Linux Capabilities는 이러한 문제를 해결하기 위해 root 권한을 더 작은 단위로 분할한 개념입니다.

각 Capability는 특정 권한을 나타내며, 프로세스에는 필요한 Capability만 정확히 부여할 수 있습니다. 이는 “최소 권한의 원칙”을 따르는 보안 관행과 완벽하게 일치합니다.

Docker에서의 포트 바인딩 문제

리눅스 시스템에서 1024 미만의 포트(잘 알려진 포트)는 보안상의 이유로 root 권한을 가진 프로세스만 사용할 수 있습니다. 웹 서버가 일반적으로 사용하는 80(HTTP)과 443(HTTPS) 포트도 여기에 해당됩니다.

Docker 컨테이너를 root가 아닌 일반 사용자로 실행할 경우, 이러한 특권 포트에 바인딩할 수 없게 됩니다. 이 문제를 해결하기 위한 몇 가지 방법이 있지만, Capabilities를 활용하는 방법이 가장 깔끔한 해결책입니다.

CAP_NET_BIND_SERVICE로 해결하기

CAP_NET_BIND_SERVICE Capability는 root가 아닌 사용자에게도 1024 미만의 포트에 바인딩할 수 있는 권한을 부여합니다. Docker Compose에서는 다음과 같이 설정할 수 있습니다:

yamlversion: '3'

services:
  webserver:
    image: nginx:latest
    container_name: my-web-server
    ports:
      - "80:80"
      - "443:443"
    cap_add:
      - NET_BIND_SERVICE
    restart: unless-stopped
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./www:/var/www/html
      - ./certbot/www:/var/www/certbot
      - ./certbot/conf:/etc/letsencrypt

이 설정의 핵심은 cap_add 섹션에서 NET_BIND_SERVICE를 추가한 부분입니다. 이를 통해 컨테이너는 비root 사용자로 실행되더라도 80, 443과 같은 특권 포트에 바인딩할 수 있게 됩니다.

추가로 비root 사용자로 명시적으로 실행하려면 다음과 같이 설정할 수 있습니다:

yamlservices:
  webserver:
    # ... 이전 설정 동일 ...
    cap_add:
      - NET_BIND_SERVICE
    user: "1000:1000"  # UID:GID 형식으로 지정

다양한 Capabilities 옵션 활용하기

Docker 보안을 강화하면서도 필요한 기능을 구현하기 위한 몇 가지 유용한 Capabilities 옵션을 소개합니다:

1. CAP_SYS_PTRACE

프로세스 추적 기능을 활성화하여 다른 프로세스의 메모리를 읽거나 쓸 수 있습니다.

yamlservices:
  debug-tool:
    image: debugging-image
    cap_add:
      - SYS_PTRACE

활용 사례: APM(Application Performance Monitoring) 도구를 실행할 때, 예를 들어 Datadog이나 New Relic 에이전트가 애플리케이션 성능을 모니터링하기 위해 필요할 수 있습니다.

2. CAP_SYS_ADMIN

시스템 관리 작업을 수행할 수 있는 강력한 권한을 부여합니다.

yamlservices:
  admin-tool:
    image: admin-image
    cap_add:
      - SYS_ADMIN

활용 사례: 특수 파일시스템을 마운트해야 하는 경우, 예를 들어 FUSE 기반 파일시스템이나 S3FS와 같은 도구를 사용할 때 필요합니다. 단, 매우 강력한 권한이므로 신중하게 사용해야 합니다.

3. CAP_NET_RAW

네트워크 패킷을 직접 조작할 수 있는 권한을 부여합니다.

yamlservices:
  network-monitor:
    image: network-tool
    cap_add:
      - NET_RAW

활용 사례: 네트워크 모니터링 도구나 와이어샤크와 같은 패킷 분석 도구를 컨테이너 내에서 실행할 때 필요합니다.

4. CAP_CHOWN

파일 소유권을 변경할 수 있는 권한입니다.

yamlservices:
  file-manager:
    image: file-tool
    cap_add:
      - CHOWN

활용 사례: 동적으로 생성된 파일의 소유권을 조정해야 하는 백업 도구나 데이터 처리 애플리케이션에서 필요합니다.

보안 강화: 불필요한 Capabilities 제거하기

컨테이너 보안을 더욱 강화하기 위해, 필요하지 않은 Capabilities는 명시적으로 제거하는 것이 좋습니다:

yamlservices:
  secure-app:
    image: my-app
    cap_drop:
      - ALL  # 모든 capabilities 제거
    cap_add:
      - NET_BIND_SERVICE  # 필요한 기능만 추가

이 접근 방식은 “화이트리스트” 전략으로, 기본적으로 모든 권한을 제거한 후 필요한 것만 명시적으로 추가합니다. 이는 보안 관점에서 가장 안전한 방법입니다.

실제 활용 사례

우리의 마이크로서비스 아키텍처에서는 다음과 같은 방식으로 Capabilities를 활용했습니다:

  1. 프론트 엔드 NGINX: 외부 요청을 처리하는 NGINX 컨테이너에는 NET_BIND_SERVICE만 부여하여 80, 443 포트를 사용할 수 있게 했습니다.
  2. 로깅 서비스: 시스템 로그를 수집하는 컨테이너에는 SYS_PTRACE를 부여하여 다른 컨테이너의 로그를 수집할 수 있게 했습니다.
  3. 백업 서비스: 데이터 백업을 담당하는 컨테이너에는 CHOWN을 부여하여 다양한 소유권을 가진 파일들을 효율적으로 처리할 수 있게 했습니다.
  4. 일반 애플리케이션: 대부분의 일반 서비스 컨테이너는 특별한 Capabilities 없이 실행하여 보안을 최대화했습니다.

마치며

Docker 컨테이너의 Capabilities를 적절히 관리함으로써, 보안을 강화하면서도 필요한 기능은 모두 구현할 수 있었습니다. “최소 권한의 원칙”에 따라 각 컨테이너에 꼭 필요한 권한만 부여함으로써, 시스템 전체의 보안 태세를 크게 향상시킬 수 있었습니다.

특히 CAP_NET_BIND_SERVICE를 통한 포트 바인딩 문제 해결은 root 권한 없이도 웹 서비스를 표준 포트로 제공할 수 있게 해주어 매우 유용했습니다.

Docker를 운영 환경에서 사용하신다면, Capabilities 시스템을 잘 이해하고 활용하는 것이 보안과 기능 사이의 최적의 균형을 찾는 데 큰 도움이 될 것입니다.

이 글이 유익했다면 공유해주세요❤️
코드 걷는 사람
코드 걷는 사람
Articles: 9

Leave a Reply

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 사이트는 Akismet을 사용하여 스팸을 줄입니다. 댓글 데이터가 어떻게 처리되는지 알아보세요.