/인프라/Container Image Security: A Complete Hardening Guide from Build to Runtime
Infrastructure컨테이너보안Docker

Container Image Security: A Complete Hardening Guide from Build to Runtime

Container image security must be managed systematically from build through runtime. This practical guide covers Docker image vulnerability scanning, SBOM generation, and runtime security policies.

Container Image Security: A Complete Hardening Guide from Build to Runtime

Why Container Image Security Matters

As container-based infrastructure has become the norm, image security is a core challenge for modern DevSecOps. According to the 2024 Sysdig report, 87% of production containers contain High or more severe vulnerabilities, many of which can be fixed with a simple base-image update.

The attack surface falls into three main areas:

  • At build time: Vulnerable base images, unnecessary packages, hardcoded secrets
  • In the registry: Unsigned images, inadequate access control
  • At runtime: Excessive privileges, sensitive mounts, abnormal process execution

Hardening the Build Stage

1. Use Minimal Base Images

Dockerfile
# 나쁜 예 — 불필요한 패키지 포함
FROM ubuntu:22.04

# 좋은 예 — distroless로 최소화
FROM gcr.io/distroless/nodejs20-debian12

# 또는 Alpine 기반
FROM node:20-alpine3.19

Alpine cuts the attack surface dramatically at around 5MB, and distroless does not even include a shell, which minimizes what an attacker can do if they compromise the container.

2. Multi-Stage Builds

Dockerfile
# 빌드 스테이지
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# 프로덕션 스테이지 — 빌드 도구 제외
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER nonroot
EXPOSE 3000
CMD ["dist/server.js"]

3. Never Run as Root

Dockerfile
# 전용 비권한 사용자 생성
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

In Kubernetes, you can enforce this with PodSecurityContext:

YAML
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false

Vulnerability Scanning: Practical Trivy Usage

Trivy is currently the most widely used open-source container scanner.

Basic Scanning

Bash
# 이미지 스캔
trivy image nginx:1.25

# 심각도 필터링 (HIGH, CRITICAL만)
trivy image --severity HIGH,CRITICAL nginx:1.25

# JSON 출력 (CI 파이프라인용)
trivy image --format json --output results.json myapp:latest

# 특정 CVE 무시 (false positive 처리)
trivy image --ignorefile .trivyignore myapp:latest

CI/CD Integration (GitHub Actions)

YAML
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.IMAGE_TAG }}
    format: sarif
    output: trivy-results.sarif
    severity: CRITICAL,HIGH
    exit-code: 1  # 취약점 발견 시 빌드 실패

- name: Upload Trivy scan results
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: trivy-results.sarif

Generating an SBOM

An SBOM (Software Bill of Materials) inventories every component included in the image:

Bash
# CycloneDX 형식으로 SBOM 생성
trivy image --format cyclonedx --output sbom.json myapp:latest

# SPDX 형식
trivy image --format spdx-json --output sbom.spdx.json myapp:latest

Image Signing: Cosign + Sigstore

Bash
# Cosign 설치 및 키 생성
cosign generate-key-pair

# 이미지 서명
cosign sign --key cosign.key registry.example.com/myapp:latest

# 서명 검증
cosign verify --key cosign.pub registry.example.com/myapp:latest

# keyless 서명 (OIDC 기반, GitHub Actions)
cosign sign --yes registry.example.com/myapp:${{ github.sha }}

To allow only signed images in Kubernetes, use a Kyverno or OPA Gatekeeper policy:

YAML
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-image
    match:
      any:
      - resources:
          kinds: [Pod]
    verifyImages:
    - imageReferences: ["registry.example.com/*"]
      attestors:
      - count: 1
        entries:
        - keys:
            publicKeys: |- 
              -----BEGIN PUBLIC KEY-----
              ...

Runtime Security: Falco

Falco detects anomalous behavior in the container runtime in real time.

YAML
# falco-rules.yaml — 주요 탐지 규칙 예시
- rule: Terminal shell in container
  desc: 컨테이너 내 셸 실행 탐지
  condition: >
    spawned_process and container and
    proc.name in (shell_binaries)
  output: >
    Shell spawned in container (user=%user.name container=%container.name
    image=%container.image.repository)
  priority: WARNING

- rule: Write below etc
  desc: /etc 디렉토리 쓰기 시도
  condition: >
    open_write and container and fd.directory=/etc
  output: File opened for writing below /etc (user=%user.name file=%fd.name)
  priority: ERROR

Registry Security Checklist

ItemRecommended setting
Access controlRBAC + least privilege
Image scanningEnable automatic scanning on push
Signing policyAllow pull of signed images only
Vulnerable imagesAuto-quarantine on CRITICAL findings
Audit loggingRecord all pull/push events
Retention policyAutomatically delete old images

Hardening Roadmap

  1. Apply immediately (1 week): Integrate Trivy into the CI pipeline; fail builds on CRITICAL vulnerabilities
  2. Short term (1 month): Multi-stage builds + migrate to distroless/Alpine; enforce non-root users
  3. Medium term (3 months): Introduce Cosign image signing; apply Kyverno signature-verification policies
  4. Long term (6 months): Falco runtime detection; complete automated SBOM generation and vulnerability tracking

Image security is not a one-time setup. New CVEs are published every day, so the key is building automation that periodically rescans deployed images and keeps base images up to date.

Vulnerability Scan Results Decision Table

When Trivy dumps hundreds of findings, trying to fix everything and ending up fixing nothing is the worst outcome.

SituationAssessmentAction
Critical + a fix is availableRespond immediatelyBump the base image/package version → rebuild and redeploy
Critical but no patch releasedAssess exposureCheck whether the library is actually on the execution path — if unused, register an exception (expiration date required); if in use, document mitigations (e.g., network isolation)
Most vulnerabilities originate from the base imageImprove the structureReplacing with a minimal image (distroless/Alpine) is more effective than patching individually
The same CVE reappears every weekProcess problemNo periodic base-image rebuild pipeline (weekly) — automate it
Judged a false positiveRecord the rationaleRegister it in .trivyignore with comments for reason, reviewer, and expiration date

Operations Checklist

  • Document CI gate criteria — e.g., Critical = block, High = warn then remediate within 7 days
  • Expiration dates on the exception (ignore) list — no permanent exceptions
  • Verify that runtime image tags match scanned image tags (scanning latest is meaningless)
  • Enforce signature verification at admission control — otherwise signing is meaningless

Frequently Asked Questions (FAQ)

Q. How do you verify container image security? A. (1) Vulnerability scanning — inspect OS and library CVEs with Trivy or Grype. (2) Image signature verification — sign and verify with Cosign to prevent tampering. (3) SBOM generation — inventory components. (4) Minimize the base image (distroless/slim) to shrink the attack surface. The key is gating scans in the CI pipeline so that a failed scan blocks deployment.

Q. When should you scan images? A. At all three stages: at build time (CI), after registry registration, and at runtime (periodic rescan). CVEs continue to be disclosed after deployment, so images that are already running must be revalidated periodically.

확인 정보
✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 · ·업데이트 ·

Comments

Be the first to comment.