본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 10장: 실전 프로젝트 — 컨테이너 보안 파이프라인 구축
2026년 3월 25일·인프라·

10장: 실전 프로젝트 — 컨테이너 보안 파이프라인 구축

빌드, 스캔, 서명, 배포, 런타임 모니터링까지 전체 컨테이너 보안 파이프라인을 GitHub Actions와 쿠버네티스 기반으로 통합 구축하는 실전 프로젝트입니다.

18분1,252자13개 섹션
securitykubernetesdevopsinfrastructure
공유
container-security10 / 10
12345678910
이전9장: 공급망 공격 방어와 제로 트러스트

학습 목표

  • 이 시리즈에서 다룬 모든 보안 도구를 하나의 파이프라인으로 통합합니다.
  • GitHub Actions CI/CD에서 빌드, 스캔, SBOM 생성, 서명을 자동화합니다.
  • 쿠버네티스에서 Kyverno 정책, Falco 런타임 감지, 네트워크 정책을 적용합니다.
  • 보안 파이프라인의 전체 체크리스트를 완성합니다.

프로젝트 개요

이번 장에서는 간단한 웹 애플리케이션을 대상으로, 빌드부터 런타임까지 완전한 보안 파이프라인을 구축합니다. 이 시리즈에서 다룬 도구와 개념을 하나로 엮는 통합 프로젝트입니다.


프로젝트 구조

프로젝트 디렉터리 구조
text
secure-app/
  cmd/
    server/
      main.go
  Dockerfile
  .github/
    workflows/
      secure-pipeline.yml
  k8s/
    deployment.yaml
    networkpolicy.yaml
    kyverno-policy.yaml
    falco-rules.yaml
  .trivyignore

1단계: 보안 Dockerfile 작성

2장에서 배운 멀티스테이지 빌드, 최소 베이스 이미지, 루트 없는 컨테이너를 모두 적용합니다.

Dockerfile
dockerfile
# syntax=docker/dockerfile:1
 
# === 빌드 스테이지 ===
FROM golang:1.22-alpine@sha256:ace6cc3fe58d0c7b12303c57afe6d6724851152df55e08b713571e8a3e2e3753 AS builder
 
RUN apk add --no-cache ca-certificates
 
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download && go mod verify
 
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-s -w -extldflags=-static" \
    -o /app/server ./cmd/server
 
# === 런타임 스테이지 ===
FROM gcr.io/distroless/static-debian12:nonroot@sha256:8dd8d3ca2cf283383304fd45a5c9c74d5f2cd9da8d3b077d720e264880077c65
 
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
 
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/server"]

핵심 보안 포인트를 정리합니다.

  • 다이제스트 고정: 베이스 이미지에 @sha256:...를 명시하여 이미지 변조를 방지합니다.
  • 멀티스테이지: 빌드 도구가 최종 이미지에 포함되지 않습니다.
  • Distroless: 셸, 패키지 매니저가 없어 공격 표면이 최소화됩니다.
  • nonroot: 루트 권한 없이 실행됩니다.
  • 정적 바이너리: 외부 라이브러리 의존성이 없습니다.

2단계: CI/CD 보안 파이프라인

3장(스캐닝), 4장(SBOM), 5장(서명)에서 배운 내용을 하나의 워크플로우로 통합합니다.

.github/workflows/secure-pipeline.yml
yaml
name: Secure Container Pipeline
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
env:
  IMAGE_NAME: ghcr.io/${{ github.repository }}
 
jobs:
  # === 빌드 및 보안 검증 ===
  build-scan-sign:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write
      security-events: write
 
    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
 
    steps:
      # 1. 체크아웃
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.1
 
      # 2. 도구 설치
      - name: Install Cosign
        uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20  # v3.5.0
 
      - name: Install Syft
        uses: anchore/sbom-action/download-syft@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1  # v0.17.0
 
      # 3. 이미지 빌드 및 푸시
      - name: Login to GHCR
        uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d  # v3.0.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Build and push
        id: build
        uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56  # v5.1.0
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: |
            ${{ env.IMAGE_NAME }}:${{ github.sha }}
            ${{ env.IMAGE_NAME }}:latest
 
      # 4. 취약점 스캐닝 (Trivy)
      - name: Trivy vulnerability scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.IMAGE_NAME }}:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          exit-code: "1"
          ignore-unfixed: true
 
      - name: Upload Trivy results
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif
 
      # 5. SBOM 생성
      - name: Generate SBOM
        if: github.event_name != 'pull_request'
        run: |
          syft ${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} \
            -o cyclonedx-json=sbom-cyclonedx.json \
            -o spdx-json=sbom-spdx.json
 
      - name: Upload SBOM artifacts
        if: github.event_name != 'pull_request'
        uses: actions/upload-artifact@v4
        with:
          name: sbom-${{ github.sha }}
          path: |
            sbom-cyclonedx.json
            sbom-spdx.json
          retention-days: 90
 
      # 6. 이미지 서명 (Cosign Keyless)
      - name: Sign image
        if: github.event_name != 'pull_request'
        run: |
          cosign sign --yes \
            ${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
 
      # 7. SBOM 첨부 및 서명
      - name: Attach and sign SBOM
        if: github.event_name != 'pull_request'
        run: |
          cosign attach sbom \
            --sbom sbom-cyclonedx.json \
            ${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
 
          cosign sign --yes \
            --attachment sbom \
            ${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
 
      # 8. 보안 검증 요약
      - name: Security summary
        if: always()
        run: |
          echo "=== 보안 파이프라인 요약 ==="
          echo "이미지: ${{ env.IMAGE_NAME }}:${{ github.sha }}"
          echo "다이제스트: ${{ steps.build.outputs.digest }}"
          echo "취약점 스캔: 완료"
          echo "SBOM 생성: 완료"
          echo "이미지 서명: 완료"
          echo "SBOM 서명: 완료"
Info

모든 서드파티 GitHub Actions의 버전을 커밋 해시로 고정했습니다. 9장에서 배운 공급망 공격 방어의 기본 원칙입니다. 해시 옆의 주석으로 실제 버전을 표기하면 가독성을 유지할 수 있습니다.


3단계: 쿠버네티스 배포 보안

보안이 적용된 Deployment

2장(SecurityContext)과 8장(시크릿 관리)의 내용을 반영합니다.

k8s/deployment.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-app
  namespace: production
  labels:
    app: secure-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: secure-app
  template:
    metadata:
      labels:
        app: secure-app
        monitoring: enabled
    spec:
      serviceAccountName: secure-app-sa
      automountServiceAccountToken: false
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534
        runAsGroup: 65534
        fsGroup: 65534
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: app
          image: ghcr.io/myorg/secure-app@sha256:abc123...
          ports:
            - containerPort: 8080
              protocol: TCP
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 256Mi
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 3
            periodSeconds: 5
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: secrets
              mountPath: /etc/app/secrets
              readOnly: true
      volumes:
        - name: tmp
          emptyDir:
            sizeLimit: 10Mi
        - name: secrets
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: secure-app-secrets

핵심 보안 설정을 정리합니다.

  • 이미지 다이제스트 고정: 태그 대신 @sha256:...로 이미지를 지정합니다.
  • automountServiceAccountToken: false: 불필요한 서비스 계정 토큰 자동 마운트를 비활성화합니다.
  • readOnlyRootFilesystem: 파일시스템 변조를 방지합니다.
  • capabilities.drop ALL: 모든 리눅스 커널 권한을 제거합니다.
  • 리소스 제한: CPU/메모리 제한으로 리소스 남용을 방지합니다.
  • CSI Secret Store: 시크릿을 안전하게 볼륨으로 마운트합니다.

4단계: 네트워크 정책

7장에서 배운 기본 거부 정책과 최소 허용 규칙을 적용합니다.

k8s/networkpolicy.yaml
yaml
# 기본 거부 — 인그레스
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
 
---
# 기본 거부 — 이그레스
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress
 
---
# DNS 이그레스 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
 
---
# Ingress 컨트롤러에서 secure-app으로의 인그레스 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-ingress-to-app
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: secure-app
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
      ports:
        - protocol: TCP
          port: 8080

5단계: Kyverno 어드미션 정책

5장(서명 검증)과 9장(공급망 방어)의 내용을 적용합니다.

k8s/kyverno-policy.yaml
yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: secure-app-policy
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    # 허용된 레지스트리만 사용
    - name: restrict-registries
      match:
        any:
          - resources:
              kinds:
                - Pod
              namespaces:
                - production
      validate:
        message: "production 네임스페이스에서는 ghcr.io/myorg/ 이미지만 허용됩니다."
        pattern:
          spec:
            containers:
              - image: "ghcr.io/myorg/*"
 
    # 이미지 서명 검증
    - name: verify-image-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
              namespaces:
                - production
      verifyImages:
        - imageReferences:
            - "ghcr.io/myorg/*"
          attestors:
            - entries:
                - keyless:
                    subject: "https://github.com/myorg/*"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: "https://rekor.sigstore.dev"
 
    # 보안 컨텍스트 필수
    - name: require-security-context
      match:
        any:
          - resources:
              kinds:
                - Pod
              namespaces:
                - production
      validate:
        message: "runAsNonRoot, readOnlyRootFilesystem 설정이 필수입니다."
        pattern:
          spec:
            containers:
              - securityContext:
                  runAsNonRoot: true
                  readOnlyRootFilesystem: true
                  allowPrivilegeEscalation: false

6단계: Falco 런타임 규칙

6장에서 배운 런타임 감지 규칙을 프로젝트에 적용합니다.

k8s/falco-rules.yaml
yaml
customRules:
  secure-app-rules.yaml: |-
    - rule: Shell in secure-app container
      desc: secure-app 컨테이너에서 셸 실행 감지
      condition: >
        spawned_process and
        container and
        k8s.ns.name = "production" and
        k8s.deployment.name = "secure-app" and
        proc.name in (sh, bash, ash, zsh)
      output: >
        secure-app 컨테이너에서 셸 실행 감지
        (user=%user.name command=%proc.cmdline
        pod=%k8s.pod.name namespace=%k8s.ns.name)
      priority: CRITICAL
      tags: [secure-app, shell_access]
 
    - rule: Unexpected network connection from secure-app
      desc: secure-app에서 예상치 못한 외부 네트워크 연결
      condition: >
        outbound and
        container and
        k8s.deployment.name = "secure-app" and
        not fd.sip.name in (allowed_dns_servers) and
        not fd.sport in (8080, 443, 53)
      output: >
        secure-app에서 예상치 못한 외부 연결
        (command=%proc.cmdline connection=%fd.name
        pod=%k8s.pod.name)
      priority: WARNING
      tags: [secure-app, network]
 
    - rule: File write attempt in secure-app
      desc: 읽기 전용 파일시스템에서의 쓰기 시도
      condition: >
        open_write and
        container and
        k8s.deployment.name = "secure-app" and
        not fd.name startswith /tmp
      output: >
        secure-app에서 파일 쓰기 시도
        (file=%fd.name command=%proc.cmdline
        pod=%k8s.pod.name)
      priority: ERROR
      tags: [secure-app, filesystem]
Tip

Distroless 이미지를 사용하므로 셸 실행 규칙이 발동하면 이는 거의 확실한 침해 징후입니다. Distroless에는 셸이 없으므로, 셸이 실행되었다는 것은 공격자가 별도로 주입했다는 의미이기 때문입니다.


전체 파이프라인 흐름

모든 단계를 통합한 전체 보안 파이프라인입니다.


보안 대시보드

운영 중인 보안 상태를 한눈에 파악하기 위한 대시보드 구성입니다.

메트릭데이터 소스시각화 도구
이미지 취약점 현황TrivyGrafana
서명 검증 결과Kyverno PolicyReportGrafana
Falco 알림 수FalcosidekickGrafana
차단된 네트워크 연결Hubble (Cilium)Grafana
SBOM 의존성 현황Dependency-Track자체 UI
시크릿 로테이션 상태VaultVault UI
kyverno-policy-report-metrics.yaml
yaml
# Kyverno PolicyReport를 Prometheus로 내보내기
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: kyverno-metrics
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: kyverno
  endpoints:
    - port: metrics
      interval: 30s
      path: /metrics

보안 파이프라인 체크리스트

이 시리즈의 전체 내용을 체크리스트로 정리합니다.

빌드 시점

  • 최소 베이스 이미지 사용 (Distroless / Chainguard)
  • 멀티스테이지 빌드로 빌드 도구 제거
  • 이미지 태그에 다이제스트 고정
  • Dockerfile에 시크릿 미포함 (BuildKit secret mount 사용)
  • Trivy/Grype로 CRITICAL/HIGH 취약점 스캔
  • SBOM 생성 (CycloneDX/SPDX)
  • Cosign 키리스 서명
  • SBOM 서명
  • SLSA Provenance 생성
  • 서드파티 Action 해시 고정

배포 시점

  • Kyverno/OPA 어드미션 정책 적용
  • 이미지 서명 검증 필수화
  • 허용된 레지스트리 목록 제한
  • latest 태그 사용 금지
  • SecurityContext 필수 (runAsNonRoot, readOnly, drop ALL)
  • 리소스 제한 설정

런타임

  • NetworkPolicy 기본 거부 설정
  • DNS 이그레스만 기본 허용
  • 필요한 통신만 명시적 허용
  • Falco DaemonSet 배포
  • 커스텀 감지 규칙 작성
  • 심각도별 알림 채널 분리
  • mTLS 적용 (서비스 메시)
  • 시크릿 외부 관리 도구 사용
  • 시크릿 볼륨 마운트 방식 사용
  • 정기 재스캐닝 설정
Info

이 체크리스트의 모든 항목을 한 번에 적용할 필요는 없습니다. 현재 보안 수준을 평가하고, 가장 영향이 큰 항목부터 단계적으로 적용하세요. 빌드 시점 보안(스캐닝, 서명)부터 시작하는 것을 권장합니다.


시리즈를 마치며

10장에 걸쳐 컨테이너 보안과 공급망 무결성의 전 영역을 다루었습니다.

1장에서 위협 모델을 이해하는 것으로 시작하여, 이미지 보안 기초(2장), 취약점 스캐닝(3장), SBOM(4장), 이미지 서명(5장)으로 빌드 시점 보안을 다졌습니다. 이어서 런타임 감지(6장), 네트워크 정책(7장), 시크릿 관리(8장)로 운영 시점 보안을 강화했고, 공급망 공격 방어(9장)로 전체적인 방어 전략을 완성했습니다.

보안은 목적지가 아니라 여정입니다. 새로운 위협은 계속 등장하고, 도구와 프레임워크도 끊임없이 발전합니다. 이 시리즈에서 구축한 보안 파이프라인을 기반으로, 자신의 환경에 맞게 지속적으로 개선해 나가시기 바랍니다.

핵심 원칙을 다시 한번 상기합니다.

  • 방어 심층(Defense in Depth): 단일 방어선에 의존하지 않습니다.
  • 최소 권한(Least Privilege): 필요한 최소한의 권한만 부여합니다.
  • 제로 트러스트(Zero Trust): 아무것도 신뢰하지 않고, 항상 검증합니다.
  • 지속적 보안(Continuous Security): 보안은 한 번이 아니라 매 배포, 매 순간 적용합니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#security#kubernetes#devops#infrastructure

관련 글

인프라

9장: 공급망 공격 방어와 제로 트러스트

의존성 혼동, 타이포스쿼팅, CI 침투 등 공급망 공격 유형을 분석하고, SLSA 프레임워크와 어드미션 컨트롤러 기반의 제로 트러스트 방어 전략을 구축합니다.

2026년 3월 23일·16분
인프라

8장: 시크릿 관리

쿠버네티스 Secrets의 한계를 이해하고, HashiCorp Vault, External Secrets Operator, Sealed Secrets로 안전한 시크릿 관리 체계를 구축합니다.

2026년 3월 21일·14분
인프라

7장: 네트워크 정책과 서비스 메시

쿠버네티스 NetworkPolicy로 기본 거부 정책을 구현하고, Calico/Cilium 네트워크 정책과 Istio mTLS로 컨테이너 간 통신을 안전하게 제어합니다.

2026년 3월 19일·14분
이전 글9장: 공급망 공격 방어와 제로 트러스트

댓글

목차

약 18분 남음
  • 학습 목표
  • 프로젝트 개요
  • 프로젝트 구조
  • 1단계: 보안 Dockerfile 작성
  • 2단계: CI/CD 보안 파이프라인
  • 3단계: 쿠버네티스 배포 보안
    • 보안이 적용된 Deployment
  • 4단계: 네트워크 정책
  • 5단계: Kyverno 어드미션 정책
  • 6단계: Falco 런타임 규칙
  • 전체 파이프라인 흐름
  • 보안 대시보드
  • 보안 파이프라인 체크리스트
    • 빌드 시점
    • 배포 시점
    • 런타임
  • 시리즈를 마치며