컨테이너 보안의 4개 레이어
컨테이너 보안은 이미지 → 런타임 → 오케스트레이터 → 네트워크 4개 레이어를 모두 잠가야 합니다. 하나라도 열려 있으면 전체가 노출됩니다.
1. 이미지 보안 — Trivy 취약점 스캔
Trivy 설치 및 기본 스캔
# 설치 (Ubuntu/Debian)
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# 이미지 스캔
trivy image nginx:latest
# CRITICAL/HIGH만 표시
trivy image --severity CRITICAL,HIGH nginx:latest
# JSON 출력 (CI 파이프라인 연동)
trivy image --format json --output result.json nginx:latest
# 로컬 Dockerfile 기반 스캔 (빌드 전)
trivy config Dockerfile
# 파일시스템 스캔 (코드 저장소)
trivy fs --severity HIGH,CRITICAL .GitHub Actions CI 통합
# .github/workflows/security.yml
name: Container Security Scan
on: [push, pull_request]
jobs:
trivy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: app:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
exit-code: 1 # CRITICAL 발견 시 빌드 실패
- name: Upload to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif2. 최소 권한 Dockerfile
# ❌ 안티패턴
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl python3
COPY . /app
CMD ["python3", "/app/server.py"]
# root로 실행, 불필요한 패키지 포함
# ✅ 프로덕션 패턴
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.12-slim
# 전용 비특권 사용자 생성
RUN groupadd -r appuser && useradd -r -g appuser -s /sbin/nologin appuser
WORKDIR /app
COPY --from=builder /install /usr/local
COPY --chown=appuser:appuser src/ .
# 쓰기 권한 제거
RUN chmod -R 555 /app
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8080/healthz || exit 1
CMD ["python3", "server.py"]이미지 크기 최소화
# Distroless: 쉘·패키지매니저 없는 최소 이미지
FROM gcr.io/distroless/python3-debian12
COPY --from=builder /app /app
WORKDIR /app
USER nonroot:nonroot
CMD ["server.py"]
# Alpine 기반 (Go 바이너리)
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache git
WORKDIR /app
COPY go.* .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o server .
FROM scratch # 빈 이미지: 쉘, 라이브러리 전혀 없음
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
USER 65534:65534 # nobody
ENTRYPOINT ["/server"]3. 런타임 보안 — Seccomp 프로파일
Seccomp은 컨테이너가 호출할 수 있는 시스템 콜을 허용 목록 방식으로 제한합니다.
// seccomp-profile.json (최소 허용 프로파일)
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": [
"read", "write", "open", "close", "stat", "fstat",
"mmap", "mprotect", "munmap", "brk",
"rt_sigaction", "rt_sigprocmask", "rt_sigreturn",
"ioctl", "access", "pipe", "select", "sched_yield",
"mremap", "msync", "mincore", "madvise",
"dup", "dup2", "pause", "nanosleep", "getitimer",
"alarm", "setitimer", "getpid", "sendfile",
"socket", "connect", "accept", "sendto", "recvfrom",
"sendmsg", "recvmsg", "shutdown", "bind", "listen",
"getsockname", "getpeername", "socketpair",
"setsockopt", "getsockopt", "clone", "fork",
"execve", "exit", "wait4", "kill", "uname",
"fcntl", "flock", "fsync", "fdatasync", "truncate",
"ftruncate", "getcwd", "chdir", "rename", "mkdir",
"rmdir", "unlink", "symlink", "readlink", "chmod",
"chown", "umask", "gettimeofday", "getrlimit",
"getrusage", "sysinfo", "times", "getuid", "getgid",
"setuid", "setgid", "geteuid", "getegid",
"setpgid", "getppid", "getpgrp", "setsid",
"getgroups", "arch_prctl", "futex", "set_tid_address",
"restart_syscall", "exit_group", "epoll_wait",
"epoll_ctl", "tgkill", "openat", "newfstatat",
"set_robust_list", "get_robust_list", "epoll_create1",
"accept4", "eventfd2", "pipe2", "prlimit64"
],
"action": "SCMP_ACT_ALLOW"
}
]
}# Seccomp 프로파일 적용
docker run --security-opt seccomp=seccomp-profile.json my-app
# 기본 Docker 프로파일로 실행 (권장)
docker run --security-opt seccomp=default my-app
# 프로파일 없이 실행하면 위험한 syscall 허용됨
# 절대 --security-opt seccomp=unconfined 사용하지 마세요4. AppArmor 프로파일 적용
# AppArmor 프로파일 생성
sudo tee /etc/apparmor.d/docker-app << 'EOF'
#include <tunables/global>
profile docker-app flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network inet tcp,
network inet udp,
file,
deny /proc/sys/kernel/** w,
deny /sys/firmware/** rwklx,
# 특정 민감 경로 차단
deny /etc/passwd w,
deny /etc/shadow rwklx,
deny /root/** rwklx,
}
EOF
sudo apparmor_parser -r /etc/apparmor.d/docker-app
# Docker 컨테이너에 AppArmor 프로파일 적용
docker run --security-opt apparmor=docker-app my-app5. 루트리스(Rootless) Docker
루트리스 모드에서는 Docker 데몬 자체가 일반 사용자 권한으로 실행됩니다.
# 루트리스 Docker 설치
curl -fsSL https://get.docker.com/rootless | sh
# 환경 변수 설정 (~/.bashrc에 추가)
export PATH=/home/$USER/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
# 부팅 시 자동 시작
systemctl --user enable docker
sudo loginctl enable-linger $USER
# 확인
docker info | grep "rootless"6. Docker Content Trust — 이미지 서명 검증
# Content Trust 활성화 (서명된 이미지만 pull/push 허용)
export DOCKER_CONTENT_TRUST=1
# 이미지 서명 후 push
docker trust key generate my-signing-key
docker trust signer add --key my-signing-key.pub myuser my-registry.com/myapp
docker push my-registry.com/myapp:1.0 # 자동으로 서명
# 서명 정보 확인
docker trust inspect --pretty my-registry.com/myapp:1.0
# 서명되지 않은 이미지는 pull 거부됨
docker pull my-registry.com/myapp:untagged # Error: no trust data7. 런타임 이상 탐지 — Falco
# Falco 설치
curl -fsSL https://falco.org/repo/falcosecurity-packages.asc | sudo gpg --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" | sudo tee /etc/apt/sources.list.d/falcosecurity.list
sudo apt update && sudo apt install -y falco# /etc/falco/falco_rules.local.yaml — 커스텀 탐지 룰
- rule: Container Shell Spawned
desc: 실행 중인 컨테이너에서 쉘이 시작됨 (잠재적 침해 신호)
condition: >
spawned_process and container and
proc.name in (bash, sh, zsh, ksh) and
not proc.pname in (containerd-shim, runc)
output: >
Shell spawned in container (user=%user.name cmd=%proc.cmdline
container=%container.name image=%container.image.repository)
priority: WARNING
tags: [container, shell, mitre_execution]
- rule: Write to /etc in Container
desc: 컨테이너 내 /etc 디렉터리 쓰기 시도
condition: >
open_write and container and
fd.name startswith /etc
output: >
Write to /etc in container (file=%fd.name container=%container.name)
priority: ERRORsudo systemctl enable --now falco
# 실시간 알림 확인
sudo journalctl -u falco -f이 가이드는 AI 도구를 활용해 초안을 구성하고 사람이 명령어·문맥을 검토해 발행했습니다. 운영체제와 도구 버전에 따라 결과가 달라질 수 있으므로 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요.
질문 & 답변 (Q&A)
이 가이드에 대해 궁금한 점을 질문해보세요. 확인 후 답변드립니다.