Docker 이미지 강화 및 공격 표면 축소를 위한 모범 사례
비루트 사용자, 더 작은 베이스, 멀티 스테이지 빌드, 비밀 처리 및 취약점 스캔을 사용하여 Docker 이미지를 강화합니다.
Docker 이미지 강화 및 공격 표면 축소를 위한 모범 사례
Docker 이미지 강화는 간단한 질문에서 시작됩니다: 이 이미지 안에 앱이 실제로 필요하지 않은 것은 무엇인가요? 추가 사용자, 셸, 패키지 관리자, 빌드 도구 및 유출된 비밀은 모두 손상된 컨테이너가 입힐 수 있는 피해를 증가시킵니다.
Dockerfile을 작성하거나 검토할 때, 특히 이미지가 공유 레지스트리나 프로덕션 클러스터에 도달하기 전에 이러한 사례를 사용하세요.
컨테이너를 비루트 사용자로 실행
가장 기본적인 보안 원칙 중 하나는 최소 권한의 원칙입니다. 기본적으로 Docker 컨테이너 내의 프로세스는 루트 사용자로 실행됩니다. 이는 광범위한 권한을 부여하며, 컨테이너가 손상될 경우 공격자가 이를 악용할 수 있습니다. 애플리케이션을 비루트 사용자로 실행하면 공격자가 컨테이너 내에서 입힐 수 있는 잠재적 피해를 크게 줄일 수 있습니다.
비루트 사용자 생성
Dockerfile 내에서 새 사용자와 그룹을 생성한 다음 애플리케이션을 실행하기 전에 해당 사용자로 전환할 수 있습니다.
# 부모 이미지로 공식 Python 런타임 사용
FROM python:3.9-slim
# 작업 디렉토리 설정
WORKDIR /app
# 현재 디렉토리 내용을 컨테이너의 /app에 복사
COPY . /app
# requirements.txt에 지정된 필요한 패키지 설치
RUN pip install --no-cache-dir -r requirements.txt
# 비루트 사용자 및 그룹 생성
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 --ingroup appgroup appuser
# 비루트 사용자로 전환
USER appuser
# 포트 80을 이 컨테이너 외부에서 사용할 수 있도록 설정
EXPOSE 80
# 환경 변수 정의
ENV NAME World
# 컨테이너 시작 시 app.py 실행
CMD ["python", "app.py"]
비루트 사용자에 대한 고려 사항
- 권한: 비루트 사용자가 애플리케이션에 필요한 디렉토리와 파일에 대해 필요한 읽기 및 쓰기 권한을 가지고 있는지 확인하세요.
chown을 사용하여 소유권을 적절히 설정해야 할 수도 있습니다. - 포트 바인딩: 비루트 사용자는 일반적으로 1024 이상의 포트에만 바인딩할 수 있습니다. 애플리케이션이 권한 있는 포트(예: 80 또는 443)에 바인딩해야 하는 경우 호스트에서 실행되는 역방향 프록시(Nginx 또는 Traefik 등)를 사용하거나 적절한 권한이 있는 다른 컨테이너 내에서 실행하거나 Linux 기능을 구성하는 것을 고려하세요.
설치된 패키지 및 종속성 최소화
Docker 이미지에 설치된 모든 패키지는 이미지 크기를 증가시키고, 더 중요한 것은 공격 표면을 증가시킵니다. 각 패키지에는 공격자가 악용할 수 있는 자체 취약점이 있을 수 있습니다. 따라서 절대적으로 필요한 것만 포함하는 것이 중요합니다.
패키지 관리를 위한 모범 사례:
- 최소 베이스 이미지 사용: 런타임에 적합한 경우
slim, distroless 또는 Alpine 기반 이미지를 고려하세요. 더 작은 이미지는 일반적으로 더 적은 패키지를 포함하지만, Alpine은 musl libc를 사용하고 Debian 또는 Ubuntu 이미지와 다르게 동작할 수 있으므로 항상 호환성을 테스트하세요. - 설치 후 정리: 패키지를 설치한 후 패키지 관리자 캐시나 임시 파일을 정리하세요. 이는 이미지 크기를 줄일 뿐만 아니라 공격자의 잠재적 준비 영역을 제거합니다.
# Debian/Ubuntu 기반 이미지 예시 RUN apt-get update && apt-get install -y --no-install-recommends some-package && \ rm -rf /var/lib/apt/lists/* # Alpine 기반 이미지 예시 RUN apk add --no-cache some-package - 멀티 스테이지 빌드: 최종 이미지를 가볍게 유지하는 강력한 기술입니다. 한 단계에서 애플리케이션을 빌드하고(빌드 도구, 컴파일러 등 설치) 두 번째 깨끗한 단계에서 빌드 단계의 필요한 아티팩트만 복사합니다. 이렇게 하면 빌드 종속성이 프로덕션 이미지에 포함되는 것을 방지할 수 있습니다.
# --- 빌드 단계 --- FROM golang:1.18-alpine AS builder WORKDIR /app COPY . . RUN go build -o myapp # --- 프로덕션 단계 --- FROM alpine:latest WORKDIR /app COPY --from=builder /app/myapp . CMD ["./myapp"] - 정기적으로 종속성 업데이트: 보안 패치를 통합하기 위해 애플리케이션 종속성과 베이스 이미지를 최신 상태로 유지하세요.
강력한 상태 확인 구현
상태 확인은 컨테이너 상태를 모니터링하는 데 중요합니다. Docker는 이러한 확인을 사용하여 컨테이너가 올바르게 실행 중인지 확인하고 비정상 컨테이너를 자동으로 다시 시작하거나 제거할 수 있습니다. 잘 정의된 상태 확인은 애플리케이션이 실행 중일 뿐만 아니라 응답하고 예상대로 작동하는지 확인하는 데 도움이 됩니다.
상태 확인 정의
Dockerfile의 HEALTHCHECK 명령어는 Docker가 컨테이너 내에서 주기적으로 실행하여 상태를 테스트할 명령을 지정합니다. 명령이 0이 아닌 상태로 종료되면 컨테이너가 비정상으로 간주됩니다.
# 웹 애플리케이션 예시
FROM nginx:latest
# ... 다른 명령어 ...
# Nginx 프로세스가 실행 중이고 포트 80에서 수신 대기 중인지 확인
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:80/ || exit 1
# ... 다른 명령어 ...
상태 확인 모범 사례:
- 간단하게 유지: 상태 확인 명령은 가볍고 빠르게 실행되어야 합니다. 확인 속도를 늦추거나 자체 실패 지점을 도입할 수 있는 복잡한 로직은 피하세요.
- 핵심 기능 테스트: 확인은 프로세스가 실행 중인지 여부뿐만 아니라 애플리케이션의 핵심 기능을 테스트해야 합니다. 웹 서버의 경우 기본 HTTP 요청에 응답할 수 있는지 확인하는 것을 의미할 수 있습니다.
start-period구성: 초기화하는 데 시간이 걸리는 애플리케이션의 경우start-period옵션을 사용하여 상태 확인이 실패하기 시작하기 전에 시작할 시간을 제공하세요.
비밀 및 민감한 데이터 안전하게 관리
API 키, 비밀번호 또는 인증서와 같은 비밀을 Dockerfile이나 이미지에 직접 포함하지 마세요. 이러한 비밀은 이미지 레이어의 일부가 되어 쉽게 발견될 수 있습니다. 대신 오케스트레이션 플랫폼(Kubernetes 또는 Docker Swarm 등)에서 관리하는 Docker secrets 또는 환경 변수를 민감한 정보에 사용하세요.
Swarm 모드의 Docker Secrets
Docker Swarm은 비밀을 관리하는 기본 메커니즘을 제공합니다. 비밀을 생성하고 파일로 컨테이너에 마운트할 수 있습니다.
# 비밀 생성
docker secret create my_api_key api_key.txt
# 비밀을 사용하여 서비스 배포
docker service create --secret my_api_key my_web_app
주의해서 환경 변수 사용
환경 변수는 편리하지만 실행 중인 컨테이너를 검사할 때도 볼 수 있습니다(docker inspect). 민감하지 않은 구성 데이터에 사용하세요. 민감한 데이터의 경우 Docker Secrets 또는 외부 비밀 관리 시스템이 선호됩니다.
특정 이미지 태그 사용
Dockerfile에서 베이스 이미지나 다른 이미지를 참조할 때(예: FROM ubuntu:latest) 항상 latest 대신 특정 버전 태그를 사용하세요. latest를 사용하면 예측할 수 없는 빌드가 발생할 수 있습니다. latest 태그는 시간이 지남에 따라 변경될 수 있으며, 사용자 모르게 호환성을 깨뜨리는 변경이나 보안 취약점을 도입할 수 있습니다.
# 피하세요:
# FROM ubuntu:latest
# 선호:
FROM ubuntu:22.04
이미지 취약점 스캔
정기적으로 Docker 이미지에서 알려진 취약점을 스캔하세요. CI/CD 파이프라인과 레지스트리에서 이를 도와주는 여러 도구가 있습니다.
인기 있는 스캐닝 도구
- Trivy: 컨테이너를 위한 간단하고 포괄적인 취약점 스캐너입니다. OS 패키지와 애플리케이션 종속성을 스캔합니다.
trivy image your-image-name:tag - Clair: 컨테이너 이미지의 취약점을 탐지하기 위한 오픈 소스 정적 분석 도구입니다.
- Docker Scout: Docker의 서비스로, 컨테이너 이미지의 취약점을 분석하고 권장 사항을 제공합니다.
이러한 스캔을 빌드 프로세스에 통합하면 이미지를 배포하기 전에 잠재적인 보안 문제를 인지하고 해결할 수 있습니다.
이미지 레이어 이해
Docker 이미지는 레이어로 빌드됩니다. Dockerfile을 변경하면 새 레이어가 생성됩니다. 레이어가 어떻게 작동하는지 이해하면 크기와 보안 모두에 대해 Dockerfile을 최적화하는 데 도움이 될 수 있습니다. 자주 변경되지 않는 명령어(예: 기본 패키지 설치)는 Dockerfile 앞부분에 배치하고, 자주 변경되는 명령어(예: 애플리케이션 코드 복사)는 뒷부분에 배치하세요. 이렇게 하면 Docker의 빌드 캐시를 효과적으로 활용하고 빌드 속도를 높일 수 있습니다.
보안 측면에서 더 중요한 것은, 이전 레이어의 민감한 정보나 우발적 노출이 지속될 수 있다는 것입니다. 더 이상 필요하지 않은 경우 민감한 파일이나 명령이 최종 이미지 레이어에 남지 않도록 처리하세요.
강화를 일상화하세요
프로덕션으로 전송되는 Dockerfile부터 시작하세요. 최종 이미지에서 빌드 도구를 제거하고, 비루트 사용자로 실행하고, 베이스 이미지를 고정하고, 모든 빌드를 스캔하세요. 그런 다음 이미지 강화를 일회성 정리가 아닌 일반 코드 검토의 일부로 취급하세요.