Theme:

컨테이너 안에서 root로 실행되는 프로세스가 있습니다. 컨테이너니까 안전하다고 생각할 수 있지만, 컨테이너 격리가 깨지면 호스트의 root 권한을 그대로 얻게 됩니다. 이 위험을 어떻게 줄일 수 있을까요?

컨테이너 보안의 기본 원칙

컨테이너는 가상 머신이 아닙니다. 호스트 커널을 공유하고, namespace와 cgroups로 격리할 뿐입니다. 따라서 방어를 여러 겹으로 쌓는 것(defense in depth)이 중요합니다.

  1. 비루트 사용자로 실행
  2. 불필요한 권한 제거
  3. 시스템 콜 제한
  4. 읽기 전용 파일 시스템
  5. rootless Docker 사용

USER 지시어 — 비루트 실행

Dockerfile에서 비루트 사용자 설정

DOCKERFILE
FROM node:20-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

COPY . .

# 비루트 사용자 생성
RUN addgroup -g 1001 appgroup \
    && adduser -u 1001 -G appgroup -s /bin/sh -D appuser

# 파일 소유권 변경
RUN chown -R appuser:appgroup /app

# 이후 모든 명령어는 appuser로 실행
USER appuser

EXPOSE 3000
CMD ["node", "server.js"]

Debian/Ubuntu 기반

DOCKERFILE
FROM python:3.12-slim

RUN groupadd -r appgroup && useradd -r -g appgroup -m appuser

WORKDIR /app
COPY --chown=appuser:appgroup . .

USER appuser
CMD ["python", "main.py"]

실행 시 사용자 지정

BASH
# Dockerfile에 USER가 없어도 실행 시 지정 가능
docker run --user 1001:1001 nginx

# 또는 사용자 이름으로
docker run --user nobody:nogroup nginx

주의사항

  • 1024 미만의 포트(80, 443 등)는 root만 바인딩할 수 있습니다. 비루트 사용자는 8080 같은 높은 포트를 사용해야 합니다.
  • 일부 이미지는 root로 실행을 전제합니다. 비루트로 전환 시 파일 퍼미션 문제가 생길 수 있습니다.
  • 볼륨 마운트 시 호스트와 컨테이너의 UID/GID가 일치해야 합니다.

Linux Capabilities — 세분화된 권한 제어

전통적인 Linux에서 root는 모든 권한을 가집니다. Capabilities는 이 권한을 약 40개로 세분화합니다.

Docker가 기본으로 허용하는 capabilities

PLAINTEXT
CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_FSETID, CAP_FOWNER,
CAP_MKNOD, CAP_NET_RAW, CAP_SETGID, CAP_SETUID,
CAP_SETFCAP, CAP_SETPCAP, CAP_NET_BIND_SERVICE,
CAP_SYS_CHROOT, CAP_KILL, CAP_AUDIT_WRITE

Docker가 기본으로 차단하는 capabilities

PLAINTEXT
CAP_SYS_ADMIN    — mount, namespace 조작 등 (가장 위험)
CAP_NET_ADMIN    — 네트워크 설정 변경
CAP_SYS_PTRACE   — 다른 프로세스 디버깅
CAP_SYS_MODULE   — 커널 모듈 로드

capabilities 제어

BASH
# 모든 capabilities 제거
docker run --cap-drop ALL myapp

# 필요한 것만 추가
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp

# 특정 capability 제거
docker run --cap-drop NET_RAW myapp

최소 권한 패턴

YAML
# compose.yaml
services:
  api:
    image: myapi:latest
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # 80, 443 포트 바인딩 필요 시
    security_opt:
      - no-new-privileges:true  # 프로세스가 추가 권한 획득 방지

no-new-privilegessetuid/setgid 비트를 통한 권한 상승을 방지합니다.

seccomp — 시스템 콜 필터링

seccomp(Secure Computing)은 컨테이너가 호출할 수 있는 시스템 콜을 제한합니다.

Docker 기본 seccomp 프로파일

Docker는 기본적으로 약 50개의 위험한 시스템 콜을 차단합니다.

BASH
# 기본 프로파일로 실행 (기본 동작)
docker run myapp

# seccomp 비활성화 (위험! 테스트 용도로만)
docker run --security-opt seccomp=unconfined myapp

# 커스텀 seccomp 프로파일 적용
docker run --security-opt seccomp=custom-profile.json myapp

차단되는 대표적인 시스템 콜

시스템 콜차단 이유
mount호스트 파일시스템 마운트 방지
reboot호스트 재부팅 방지
clock_settime시스템 시간 변경 방지
kernel_module커널 모듈 로드 방지
unshare새 네임스페이스 생성 방지

커스텀 seccomp 프로파일 예시

JSON
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": ["read", "write", "open", "close", "stat", "fstat",
                "lstat", "poll", "lseek", "mmap", "mprotect",
                "munmap", "brk", "ioctl", "access", "pipe",
                "select", "sched_yield", "mremap", "msync",
                "mincore", "madvise", "dup", "dup2", "nanosleep",
                "socket", "connect", "accept", "sendto", "recvfrom",
                "bind", "listen", "getsockname", "getpeername",
                "clone", "fork", "vfork", "execve", "exit",
                "wait4", "kill", "uname", "getcwd", "chdir",
                "rename", "mkdir", "rmdir", "link", "unlink",
                "chmod", "chown", "getuid", "getgid", "geteuid",
                "getegid", "getpid", "getppid"],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

AppArmor와 SELinux

AppArmor (Ubuntu/Debian)

Docker는 기본 AppArmor 프로파일(docker-default)을 자동 적용합니다.

BASH
# AppArmor 상태 확인
sudo aa-status

# 기본 프로파일 확인
docker inspect mycontainer --format '{{.HostConfig.SecurityOpt}}'

# 커스텀 프로파일 적용
docker run --security-opt apparmor=my-custom-profile myapp

# AppArmor 비활성화 (테스트 용도만)
docker run --security-opt apparmor=unconfined myapp

SELinux (CentOS/RHEL/Fedora)

BASH
# SELinux 상태 확인
getenforce

# SELinux 라벨로 볼륨 마운트
docker run -v /data:/data:Z myapp     # 프라이빗 라벨
docker run -v /data:/data:z myapp     # 공유 라벨

Rootless Docker

일반 Docker vs Rootless Docker

PLAINTEXT
일반 Docker:
┌── root ────────────────────────┐
│  dockerd (root)                │
│  ├── containerd (root)         │
│  │   └── runc (root)           │
│  │       └── 컨테이너 (root)    │
│  └── docker-proxy (root)       │
└────────────────────────────────┘

Rootless Docker:
┌── 일반 사용자 (UID 1000) ──────┐
│  dockerd (UID 1000)            │
│  ├── containerd (UID 1000)     │
│  │   └── runc (UID 1000)       │
│  │       └── 컨테이너           │
│  └── docker-proxy (UID 1000)   │
└────────────────────────────────┘

설치

BASH
# 사전 요구사항 설치
sudo apt-get install -y uidmap dbus-user-session

# rootless Docker 설치
dockerd-rootless-setuptool.sh install

# 환경변수 설정
export PATH=/home/myuser/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

Rootless의 제한사항

기능일반Rootless
1024 미만 포트 바인딩OX (slirp4netns로 우회)
overlay2 스토리지OO (커널 5.11+)
--net=hostOX
cgroups v1O제한적
AppArmor/SELinuxO제한적

Docker Desktop

Docker Desktop(macOS, Windows)은 이미 VM 내부에서 실행되므로 rootless와는 다른 격리 수준을 제공합니다.

실전 보안 체크리스트

YAML
# compose.yaml — 보안 강화 예시
services:
  api:
    image: myapi:latest
    user: "1001:1001"

    # 권한 최소화
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true

    # 읽기 전용 파일 시스템
    read_only: true
    tmpfs:
      - /tmp
      - /var/run

    # 리소스 제한
    deploy:
      resources:
        limits:
          cpus: "0.5"
          memory: 512M
        reservations:
          memory: 256M

    # PID 수 제한 (fork bomb 방지)
    pids_limit: 100

    # 네트워크
    networks:
      - backend

    # 재시작 정책
    restart: unless-stopped

추가 보안 옵션

BASH
# 프로세스 격리
docker run --pid=private myapp     # PID 네임스페이스 격리

# IPC 격리
docker run --ipc=private myapp     # IPC 네임스페이스 격리

# 읽기 전용 루트 파일시스템
docker run --read-only --tmpfs /tmp myapp

# PID 제한
docker run --pids-limit 100 myapp

# 메모리 스왑 비활성화
docker run --memory 512m --memory-swap 512m myapp

정리

  • USER 지시어로 컨테이너 프로세스를 비루트 사용자로 실행합니다. 가장 기본적이면서도 효과적인 보안 조치입니다.
  • capabilitiesALL 제거 후 필요한 것만 추가하는 방식으로 권한을 최소화합니다.
  • seccomp은 기본 프로파일이 자동 적용되어 위험한 시스템 콜을 차단합니다.
  • no-new-privileges로 프로세스의 권한 상승을 방지합니다.
  • Rootless Docker는 Docker 데몬 자체를 비루트로 실행하여 가장 강력한 보안을 제공하지만, 일부 기능 제한이 있습니다.
  • 이런 보안 조치들은 단일로는 완벽하지 않지만, 겹겹이 쌓으면 공격 표면을 크게 줄일 수 있습니다.
댓글 로딩 중...