Split View: 컨테이너 보안 & 공급망 보안 완전 가이드 2025: 이미지 스캐닝, Sigstore, SBOM, 런타임 보안
컨테이너 보안 & 공급망 보안 완전 가이드 2025: 이미지 스캐닝, Sigstore, SBOM, 런타임 보안
목차
1. 컨테이너 위협 모델: 공격 표면 이해
컨테이너 환경은 기존 VM 기반 인프라와 다른 고유한 위협 표면을 가진다. 이미지 취약점, 설정 오류, 런타임 공격, 공급망 위협까지 다층적 보안 전략이 필요하다.
1.1 컨테이너 공격 표면
컨테이너 보안 레이어:
┌─────────────────────────────────────────────────┐
│ Layer 6: 공급망 (Supply Chain) │
│ - 베이스 이미지 오염, 의존성 변조, CI/CD 파이프라인 │
├─────────────────────────────────────────────────┤
│ Layer 5: 오케스트레이션 (Kubernetes) │
│ - RBAC 미설정, API 서버 노출, etcd 미암호화 │
├─────────────────────────────────────────────────┤
│ Layer 4: 네트워크 │
│ - 네트워크 정책 미설정, 서비스 메시 미적용 │
├─────────────────────────────────────────────────┤
│ Layer 3: 런타임 │
│ - 컨테이너 탈출, 권한 상승, 암호화폐 채굴 │
├─────────────────────────────────────────────────┤
│ Layer 2: 이미지 │
│ - CVE 취약점, 하드코딩된 시크릿, 과도한 패키지 │
├─────────────────────────────────────────────────┤
│ Layer 1: 호스트 OS / 컨테이너 런타임 │
│ - 커널 취약점, Docker 소켓 노출, runc 취약점 │
└─────────────────────────────────────────────────┘
1.2 주요 공격 벡터
1. 이미지 취약점 악용:
공격자 → 공개 이미지의 알려진 CVE 악용 → 컨테이너 침투
2. 공급망 공격:
공격자 → 베이스 이미지/라이브러리에 악성코드 주입 → 다운스트림 오염
3. 런타임 공격:
공격자 → 취약한 앱 악용 → 컨테이너 탈출 → 호스트 접근
4. 설정 오류 악용:
공격자 → 과도한 권한/미설정 네트워크 정책 → 횡적 이동
2. 이미지 보안: 안전한 컨테이너 이미지 구축
2.1 최소 베이스 이미지 선택
# BAD: 풀 이미지 사용 (200MB+, 수백 개 CVE 가능)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3
COPY app.py /app/
CMD ["python3", "/app/app.py"]
# GOOD: Distroless 이미지 (최소한의 바이너리만 포함)
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
FROM gcr.io/distroless/python3-debian12
COPY /app /app
COPY /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
WORKDIR /app
CMD ["app.py"]
2.2 Chainguard 이미지
# Chainguard Images: 매일 리빌드, 제로 CVE 목표
FROM cgr.dev/chainguard/python:latest-dev AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
FROM cgr.dev/chainguard/python:latest
COPY /app /app
COPY /home/nonroot/.local/lib/python3.12/site-packages /home/nonroot/.local/lib/python3.12/site-packages
WORKDIR /app
ENTRYPOINT ["python", "app.py"]
2.3 멀티스테이지 빌드와 보안
# Go 앱 멀티스테이지 빌드
FROM golang:1.22-alpine AS builder
# 보안: 빌드 시에만 필요한 도구 설치
RUN apk add --no-cache git ca-certificates
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# 보안: 정적 바이너리 빌드 (CGO 비활성화)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s" -o /app/server ./cmd/server
# 최종 이미지: scratch (완전히 비어있는 이미지)
FROM scratch
# 보안: 비 root 사용자
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY /etc/passwd /etc/passwd
COPY /app/server /server
USER 65534:65534
ENTRYPOINT ["/server"]
2.4 Dockerfile 보안 베스트 프랙티스
# 보안 강화된 Dockerfile 예시
FROM node:20-slim AS builder
# 보안: 비 root 사용자 생성
RUN groupadd -r appuser && useradd -r -g appuser -d /app appuser
WORKDIR /app
# 보안: package.json만 먼저 복사 (레이어 캐시 활용)
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production
COPY . .
RUN yarn build
# 프로덕션 이미지
FROM node:20-slim
# 보안: 비 root 사용자
RUN groupadd -r appuser && useradd -r -g appuser -d /app appuser
WORKDIR /app
# 보안: 필요한 파일만 복사
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY /app/package.json ./
# 보안: 비 root로 실행
USER appuser
# 보안: 읽기 전용 파일시스템 호환
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/index.js"]
3. 이미지 스캐닝: 취약점 탐지
3.1 스캐닝 도구 비교
| 도구 | 라이센스 | 특징 | CI/CD 통합 | SBOM 생성 |
|---|---|---|---|---|
| Trivy | Apache 2.0 | 종합 스캐너(이미지/IaC/시크릿) | 우수 | 지원 |
| Grype | Apache 2.0 | 빠른 취약점 스캔 | 우수 | syft 연동 |
| Docker Scout | 프리미엄 | Docker Desktop 통합 | Docker Hub | 지원 |
| Snyk Container | 프리미엄 | 수정 권고 우수 | 우수 | 지원 |
3.2 Trivy 사용법
# 이미지 스캔
trivy image nginx:1.25
# 심각도 필터링
trivy image --severity HIGH,CRITICAL nginx:1.25
# SARIF 형식 출력 (GitHub Security 연동)
trivy image --format sarif --output trivy-results.sarif nginx:1.25
# 시크릿 스캔
trivy image --scanners secret myapp:latest
# IaC 스캔 (Dockerfile, Kubernetes YAML)
trivy config .
# SBOM 생성
trivy image --format cyclonedx --output sbom.json nginx:1.25
# .trivyignore - 특정 CVE 무시
# 이유를 주석으로 기록
# CVE-2023-xxxx: 우리 환경에서는 영향 없음 (해당 기능 미사용)
CVE-2023-xxxx
3.3 Grype와 syft
# syft로 SBOM 생성
syft packages nginx:1.25 -o cyclonedx-json > sbom.json
# Grype로 SBOM 기반 스캔
grype sbom:./sbom.json
# Grype 직접 이미지 스캔
grype nginx:1.25
# 심각도 임계값 설정 (CI/CD에서 사용)
grype nginx:1.25 --fail-on critical
3.4 CI/CD 통합 스캐닝
# .github/workflows/image-scan.yml
name: Container Image Scan
on:
push:
branches: [main]
pull_request:
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:ci .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:ci'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Run Grype scan
uses: anchore/scan-action@v4
with:
image: 'myapp:ci'
fail-build: true
severity-cutoff: 'high'
4. 이미지 서명: Sigstore와 cosign
4.1 Sigstore 생태계
Sigstore 구성 요소:
┌──────────────────────────────────────────────────┐
│ cosign │
│ - 컨테이너 이미지/아티팩트 서명 및 검증 │
│ - 키리스(Keyless) 서명 지원 (OIDC 기반) │
├──────────────────────────────────────────────────┤
│ Rekor │
│ - 불변 투명성 로그 (Transparency Log) │
│ - 모든 서명 기록을 공개적으로 저장 │
├──────────────────────────────────────────────────┤
│ Fulcio │
│ - 단기 인증서 발급 (Certificate Authority) │
│ - OIDC 토큰을 X.509 인증서로 변환 │
└──────────────────────────────────────────────────┘
4.2 cosign으로 이미지 서명
# 키 쌍 생성
cosign generate-key-pair
# 이미지 서명
cosign sign --key cosign.key myregistry.com/myapp:v1.0
# 이미지 서명 검증
cosign verify --key cosign.pub myregistry.com/myapp:v1.0
# 키리스 서명 (CI/CD에서 권장)
# OIDC 토큰으로 자동 인증 (GitHub Actions, GitLab CI)
cosign sign myregistry.com/myapp:v1.0
# 키리스 검증
cosign verify \
--certificate-identity "https://github.com/myorg/myapp/.github/workflows/build.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
myregistry.com/myapp:v1.0
4.3 GitHub Actions에서 키리스 서명
# .github/workflows/sign-image.yml
name: Build, Sign, and Push
on:
push:
tags: ['v*']
permissions:
contents: read
packages: write
id-token: write # 키리스 서명에 필요
jobs:
build-sign-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: sigstore/cosign-installer@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: "${{ github.actor }}"
password: "${{ secrets.GITHUB_TOKEN }}"
- name: Build and push
id: build
uses: docker/build-push-action@v5
with:
push: true
tags: "ghcr.io/${{ github.repository }}:${{ github.ref_name }}"
- name: Sign the image (keyless)
run: |
cosign sign --yes \
"ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}"
4.4 Kubernetes Admission Controller로 서명 검증
# Kyverno 정책: 서명된 이미지만 허용
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/myorg/*"
attestors:
- entries:
- keyless:
subject: "https://github.com/myorg/*/.github/workflows/*@refs/heads/main"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: "https://rekor.sigstore.dev"
5. SBOM (Software Bill of Materials)
5.1 SBOM 포맷 비교
CycloneDX:
- OWASP 프로젝트
- 보안에 초점 (취약점, 라이센스)
- JSON, XML 포맷
- VEX (Vulnerability Exploitability eXchange) 지원
SPDX:
- Linux Foundation 프로젝트
- 라이센스 준수에 초점
- JSON, RDF, Tag-Value 포맷
- ISO/IEC 5962:2021 국제 표준
5.2 syft로 SBOM 생성
# CycloneDX 포맷으로 SBOM 생성
syft packages myapp:latest -o cyclonedx-json > sbom-cyclonedx.json
# SPDX 포맷으로 SBOM 생성
syft packages myapp:latest -o spdx-json > sbom-spdx.json
# 디렉토리 스캔
syft dir:./my-project -o cyclonedx-json > project-sbom.json
# SBOM을 이미지에 첨부 (cosign + SBOM)
cosign attach sbom --sbom sbom-cyclonedx.json myregistry.com/myapp:v1.0
# SBOM attestation 생성
cosign attest --predicate sbom-cyclonedx.json \
--type cyclonedx \
myregistry.com/myapp:v1.0
5.3 SBOM 기반 취약점 관리
# SBOM에서 취약점 스캔
grype sbom:./sbom-cyclonedx.json
# 특정 패키지 조회
syft packages myapp:latest | grep log4j
# 라이센스 분석
syft packages myapp:latest -o json | \
jq '.artifacts[].licenses[].value' | sort | uniq -c | sort -rn
6. Pod Security Standards
6.1 보안 수준 비교
Privileged (특권):
- 제한 없음
- 알려진 권한 상승 허용
- 시스템 데몬, CNI 플러그인용
Baseline (기본):
- 알려진 권한 상승 방지
- hostNetwork, hostPID, hostIPC 차단
- 특권 컨테이너 차단
- 대부분의 워크로드에 적합
Restricted (제한):
- 최소 권한 원칙 적용
- 비 root 강제
- 모든 capability 삭제
- seccomp 프로필 필수
- 보안이 중요한 워크로드용
6.2 Pod Security Admission 설정
# Namespace에 Pod Security Standards 적용
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# Restricted 수준 강제
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# Restricted 수준 경고
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
# Restricted 수준 감사
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
6.3 보안 강화된 Pod 명세
# Restricted 수준을 만족하는 Pod 명세
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
# 보안: 서비스 어카운트 토큰 자동 마운트 비활성화
automountServiceAccountToken: false
securityContext:
# 보안: 비 root 그룹으로 실행
runAsGroup: 1000
fsGroup: 1000
# 보안: Seccomp 프로필 적용
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: ghcr.io/myorg/secure-app:v1.0
ports:
- containerPort: 8080
protocol: TCP
securityContext:
# 보안: 비 root 사용자로 실행
runAsNonRoot: true
runAsUser: 1000
# 보안: 읽기 전용 루트 파일시스템
readOnlyRootFilesystem: true
# 보안: 권한 상승 차단
allowPrivilegeEscalation: false
# 보안: 모든 capability 삭제
capabilities:
drop:
- ALL
# 임시 쓰기 디렉토리
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/cache
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: tmp
emptyDir:
sizeLimit: 100Mi
- name: cache
emptyDir:
sizeLimit: 200Mi
7. 네트워크 정책 (Network Policy)
7.1 기본 거부 정책
# 모든 인그레스/이그레스 차단 (기본 거부)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
7.2 세밀한 네트워크 정책
# API 서비스: 특정 소스에서만 인그레스 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-service-policy
namespace: production
spec:
podSelector:
matchLabels:
app: api-service
policyTypes:
- Ingress
- Egress
ingress:
# 인그레스 컨트롤러에서만 허용
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
podSelector:
matchLabels:
app: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
# 데이터베이스 접근 허용
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
# DNS 허용
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
7.3 Cilium 네트워크 정책
# Cilium L7 네트워크 정책 (HTTP 경로 기반)
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-l7-policy
namespace: production
spec:
endpointSelector:
matchLabels:
app: api-service
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: "GET"
path: "/api/v1/products"
- method: "POST"
path: "/api/v1/orders"
8. 런타임 보안: Falco와 Tetragon
8.1 Falco 규칙
# Falco 커스텀 규칙
- rule: Detect Reverse Shell
desc: 컨테이너에서 리버스 셸 연결 시도 탐지
condition: >
evt.type=connect and
evt.dir=< and
container and
fd.typechar=4 and
fd.rip != "127.0.0.1" and
proc.name in (bash, sh, zsh, dash, csh)
output: >
리버스 셸 탐지 (user=%user.name command=%proc.cmdline
connection=%fd.name container=%container.name
image=%container.image.repository)
priority: CRITICAL
tags: [network, shell, mitre_execution]
- rule: Detect Cryptomining
desc: 컨테이너에서 암호화폐 채굴 프로세스 탐지
condition: >
spawned_process and
container and
(proc.name in (xmrig, minerd, minergate, cpuminer) or
proc.cmdline contains "stratum+tcp" or
proc.cmdline contains "cryptonight")
output: >
암호화폐 채굴 탐지 (user=%user.name command=%proc.cmdline
container=%container.name image=%container.image.repository)
priority: CRITICAL
tags: [crypto, mining, mitre_resource_hijacking]
- rule: Sensitive File Access
desc: 민감한 파일 접근 탐지
condition: >
open_read and
container and
(fd.name startswith /etc/shadow or
fd.name startswith /etc/passwd or
fd.name startswith /proc/self/environ or
fd.name startswith /run/secrets)
output: >
민감 파일 접근 (user=%user.name file=%fd.name
command=%proc.cmdline container=%container.name)
priority: WARNING
tags: [filesystem, mitre_credential_access]
8.2 Falco Helm 배포
# Falco 설치
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
helm install falco falcosecurity/falco \
--namespace falco --create-namespace \
--set falcosidekick.enabled=true \
--set falcosidekick.config.slack.webhookurl="https://hooks.slack.com/services/xxx" \
--set falcosidekick.config.slack.minimumpriority=warning \
--set driver.kind=ebpf
8.3 Tetragon eBPF 정책
# Tetragon 정책: 프로세스 실행 모니터링
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: monitor-process-execution
spec:
kprobes:
- call: "security_bprm_check"
syscall: false
args:
- index: 0
type: "linux_binprm"
selectors:
- matchBinaries:
- operator: "In"
values:
- "/bin/bash"
- "/bin/sh"
- "/usr/bin/curl"
- "/usr/bin/wget"
matchNamespaces:
- namespace: Mnt
operator: NotIn
values:
- "host_mnt_ns"
---
# Tetragon 정책: 네트워크 연결 모니터링
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: monitor-network-connections
spec:
kprobes:
- call: "tcp_connect"
syscall: false
args:
- index: 0
type: "sock"
selectors:
- matchArgs:
- index: 0
operator: "NotDAddr"
values:
- "127.0.0.1"
- "10.0.0.0/8"
9. 시크릿 관리
9.1 External Secrets Operator
# SecretStore 설정 (AWS Secrets Manager)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-2
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
# ExternalSecret 정의
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/database
property: username
- secretKey: password
remoteRef:
key: prod/database
property: password
9.2 Vault CSI Provider
# Vault SecretProviderClass
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-db-creds
namespace: production
spec:
provider: vault
parameters:
vaultAddress: "https://vault.example.com"
roleName: "app-role"
objects: |
- objectName: "db-username"
secretPath: "secret/data/prod/database"
secretKey: "username"
- objectName: "db-password"
secretPath: "secret/data/prod/database"
secretKey: "password"
---
# Pod에서 사용
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
serviceAccountName: app-sa
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: secrets
mountPath: "/mnt/secrets"
readOnly: true
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: vault-db-creds
10. SLSA 프레임워크
10.1 SLSA 레벨
SLSA Level 1 - Provenance:
- 빌드 프로세스가 문서화됨
- 자동화된 빌드
SLSA Level 2 - Build Service:
- 호스팅된 빌드 서비스 사용
- 서명된 provenance 생성
- 버전 관리 소스
SLSA Level 3 - Build Integrity:
- 격리된 빌드 환경
- 변조 방지 provenance
- 소스 무결성 보장
SLSA Level 4 - Dependencies:
- 모든 종속성에 대한 재귀적 보증
- 재현 가능한 빌드
- (현재 이론적 단계)
10.2 SLSA Provenance 생성
# GitHub Actions에서 SLSA Provenance 생성
name: SLSA Build
on:
push:
tags: ['v*']
permissions:
contents: read
packages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: "${{ steps.build.outputs.digest }}"
steps:
- uses: actions/checkout@v4
- name: Build and push
id: build
uses: docker/build-push-action@v5
with:
push: true
tags: "ghcr.io/${{ github.repository }}:${{ github.ref_name }}"
provenance:
needs: build
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with:
image: "ghcr.io/${{ github.repository }}"
digest: "${{ needs.build.outputs.digest }}"
secrets:
registry-username: "${{ github.actor }}"
registry-password: "${{ secrets.GITHUB_TOKEN }}"
11. CI/CD 보안 파이프라인
11.1 전체 파이프라인 구성
보안 CI/CD 파이프라인:
┌─────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│Build│ → │ Scan │ → │ Sign │ → │Attest│ → │Verify│ → │Deploy│
└─────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘
│ │ │ │ │ │
│ │ │ │ │ │
빌드 취약점 cosign SBOM Kyverno 서명 검증
이미지 스캔 서명 attestation 정책 후 배포
Trivy SLSA 검증
Grype provenance
11.2 CIS 벤치마크와 Kubescape
# kube-bench: CIS Kubernetes Benchmark 실행
kube-bench run --targets master,node
# Kubescape: NSA, MITRE, CIS 프레임워크 스캔
kubescape scan framework cis-v1.23 --exclude-namespaces kube-system
# 특정 컨트롤 스캔
kubescape scan control C-0002 --namespace production
# Kubescape YAML 스캔
kubescape scan .
12. 퀴즈
다음 퀴즈로 이 글에서 다룬 컨테이너 보안에 대한 이해를 점검해 보자.
Q1. 이미지 보안
질문: Distroless 이미지와 Alpine 이미지의 차이점은 무엇인가?
정답: Distroless 이미지는 셸(bash/sh)과 패키지 매니저가 없는 최소 이미지다. Alpine은 musl libc 기반의 경량 Linux 배포판으로, 셸과 패키지 매니저(apk)를 포함한다.
Distroless는 공격 표면이 더 작지만 디버깅이 어렵다. Alpine은 디버깅은 가능하지만 musl libc 호환성 문제가 발생할 수 있다. 프로덕션에서는 Distroless나 Chainguard 이미지를 권장하고, 디버깅이 필요한 경우 debug 태그 변종을 사용한다.
Q2. Sigstore 키리스 서명
질문: cosign 키리스 서명이 기존 키 기반 서명보다 CI/CD에서 선호되는 이유는 무엇인가?
정답: 키리스 서명은 OIDC 토큰(예: GitHub Actions의 워크플로 ID 토큰)을 사용하므로, 장기 비밀키를 관리하고 보호할 필요가 없다.
Fulcio가 OIDC 토큰을 단기 인증서로 변환하고, Rekor 투명성 로그에 서명이 기록된다. 키 순환, 키 유출, 키 보관 문제가 없어 CI/CD 환경에서 운영 부담이 크게 줄어든다.
Q3. Pod Security Standards
질문: Pod Security Standards의 Restricted 수준에서 반드시 충족해야 하는 보안 요구사항 3가지를 나열하시오.
정답:
- 비 root 사용자로 실행 (runAsNonRoot: true)
- 모든 Linux capability 삭제 (capabilities.drop: ALL)
- 권한 상승 차단 (allowPrivilegeEscalation: false)
추가적으로, seccomp 프로필 적용(RuntimeDefault 또는 Localhost), 읽기 전용 루트 파일시스템(readOnlyRootFilesystem: true)도 Restricted 수준의 권장 사항이다.
Q4. 런타임 보안
질문: Falco와 Tetragon의 아키텍처 차이를 설명하시오.
정답:
- Falco: 커널 모듈 또는 eBPF 드라이버로 시스템 콜을 캡처하고, 사용자 공간의 규칙 엔진에서 이벤트를 분석한다. 탐지(detection) 중심으로, 규칙에 매칭되면 알림을 생성한다.
- Tetragon: 순수 eBPF 기반으로 커널 수준에서 직접 정책을 실행한다. 탐지뿐만 아니라 차단(enforcement)도 가능하며, 프로세스 실행, 네트워크 연결, 파일 접근을 커널에서 직접 제어할 수 있다.
Q5. SLSA 프레임워크
질문: SLSA Level 3에서 요구하는 핵심 보안 속성 2가지는 무엇인가?
정답:
- 격리된 빌드 환경: 빌드가 다른 빌드와 격리된 환경에서 실행되어, 빌드 간 오염을 방지한다.
- 변조 방지(tamper-resistant) provenance: 빌드 시스템이 생성한 provenance를 빌드 자체가 수정할 수 없다. provenance는 빌드 서비스에 의해 서명되며, 소스와 빌드 프로세스의 무결성을 보장한다.
13. 참고 자료
- Trivy - https://aquasecurity.github.io/trivy/
- Sigstore (cosign, Rekor, Fulcio) - https://www.sigstore.dev/
- SLSA Framework - https://slsa.dev/
- Falco - https://falco.org/docs/
- Tetragon - https://tetragon.io/docs/
- Grype - https://github.com/anchore/grype
- syft - https://github.com/anchore/syft
- CycloneDX - https://cyclonedx.org/
- SPDX - https://spdx.dev/
- Kyverno - https://kyverno.io/docs/
- External Secrets Operator - https://external-secrets.io/
- Kubescape - https://kubescape.io/
- Chainguard Images - https://www.chainguard.dev/chainguard-images
- CIS Benchmarks - https://www.cisecurity.org/benchmark/kubernetes
이 글에서는 컨테이너 보안의 전체 라이프사이클을 다루었다. 이미지 보안(최소 베이스 이미지, 스캐닝), 서명 및 검증(Sigstore/cosign), SBOM, Pod Security Standards, 네트워크 정책, 런타임 보안(Falco/Tetragon), 시크릿 관리, SLSA 프레임워크, CI/CD 보안 파이프라인까지 포괄적으로 살펴보았다. 다층 방어(Defense in Depth) 전략을 채택하고, 보안을 개발 초기부터 CI/CD 파이프라인에 통합하는 것이 핵심이다.
Container Security & Supply Chain Complete Guide 2025: Image Scanning, Sigstore, SBOM, Runtime Security
Table of Contents
1. Container Threat Model: Understanding the Attack Surface
Container environments have a unique threat surface distinct from traditional VM-based infrastructure. A multi-layered security strategy is needed covering image vulnerabilities, misconfigurations, runtime attacks, and supply chain threats.
1.1 Container Attack Surface
Container Security Layers:
┌─────────────────────────────────────────────────┐
│ Layer 6: Supply Chain │
│ - Base image poisoning, dependency tampering │
├─────────────────────────────────────────────────┤
│ Layer 5: Orchestration (Kubernetes) │
│ - Missing RBAC, exposed API server, unencrypted │
├─────────────────────────────────────────────────┤
│ Layer 4: Network │
│ - Missing network policies, no service mesh │
├─────────────────────────────────────────────────┤
│ Layer 3: Runtime │
│ - Container escape, privilege escalation, mining │
├─────────────────────────────────────────────────┤
│ Layer 2: Image │
│ - CVEs, hardcoded secrets, excessive packages │
├─────────────────────────────────────────────────┤
│ Layer 1: Host OS / Container Runtime │
│ - Kernel vulns, Docker socket exposure, runc │
└─────────────────────────────────────────────────┘
1.2 Key Attack Vectors
1. Image Vulnerability Exploitation:
Attacker -> Exploit known CVEs in public images -> Container compromise
2. Supply Chain Attack:
Attacker -> Inject malware in base image/library -> Downstream poisoning
3. Runtime Attack:
Attacker -> Exploit vulnerable app -> Container escape -> Host access
4. Misconfiguration Exploitation:
Attacker -> Excessive permissions/missing policies -> Lateral movement
2. Image Security: Building Secure Container Images
2.1 Choosing Minimal Base Images
# BAD: Full image (200MB+, potentially hundreds of CVEs)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3
COPY app.py /app/
CMD ["python3", "/app/app.py"]
# GOOD: Distroless image (minimal binaries only)
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
FROM gcr.io/distroless/python3-debian12
COPY /app /app
COPY /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
WORKDIR /app
CMD ["app.py"]
2.2 Chainguard Images
# Chainguard Images: rebuilt daily, zero-CVE goal
FROM cgr.dev/chainguard/python:latest-dev AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
FROM cgr.dev/chainguard/python:latest
COPY /app /app
COPY /home/nonroot/.local/lib/python3.12/site-packages /home/nonroot/.local/lib/python3.12/site-packages
WORKDIR /app
ENTRYPOINT ["python", "app.py"]
2.3 Multi-Stage Builds and Security
# Go app multi-stage build
FROM golang:1.22-alpine AS builder
# Security: install build-only tools
RUN apk add --no-cache git ca-certificates
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Security: static binary (CGO disabled)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s" -o /app/server ./cmd/server
# Final image: scratch (completely empty)
FROM scratch
# Security: non-root user
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY /etc/passwd /etc/passwd
COPY /app/server /server
USER 65534:65534
ENTRYPOINT ["/server"]
2.4 Dockerfile Security Best Practices
# Security-hardened Dockerfile
FROM node:20-slim AS builder
# Security: create non-root user
RUN groupadd -r appuser && useradd -r -g appuser -d /app appuser
WORKDIR /app
# Security: copy package.json first (layer caching)
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production
COPY . .
RUN yarn build
# Production image
FROM node:20-slim
# Security: non-root user
RUN groupadd -r appuser && useradd -r -g appuser -d /app appuser
WORKDIR /app
# Security: copy only necessary files
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY /app/package.json ./
# Security: run as non-root
USER appuser
# Security: read-only filesystem compatible
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/index.js"]
3. Image Scanning: Vulnerability Detection
3.1 Scanning Tool Comparison
| Tool | License | Features | CI/CD Integration | SBOM Generation |
|---|---|---|---|---|
| Trivy | Apache 2.0 | Comprehensive scanner (image/IaC/secrets) | Excellent | Supported |
| Grype | Apache 2.0 | Fast vulnerability scan | Excellent | syft integration |
| Docker Scout | Premium | Docker Desktop integration | Docker Hub | Supported |
| Snyk Container | Premium | Excellent fix recommendations | Excellent | Supported |
3.2 Using Trivy
# Image scan
trivy image nginx:1.25
# Filter by severity
trivy image --severity HIGH,CRITICAL nginx:1.25
# SARIF output (GitHub Security integration)
trivy image --format sarif --output trivy-results.sarif nginx:1.25
# Secret scan
trivy image --scanners secret myapp:latest
# IaC scan (Dockerfile, Kubernetes YAML)
trivy config .
# Generate SBOM
trivy image --format cyclonedx --output sbom.json nginx:1.25
# .trivyignore - ignore specific CVEs
# Document reason in comments
# CVE-2023-xxxx: Not applicable in our environment (feature not used)
CVE-2023-xxxx
3.3 Grype and syft
# Generate SBOM with syft
syft packages nginx:1.25 -o cyclonedx-json > sbom.json
# Scan SBOM with Grype
grype sbom:./sbom.json
# Direct image scan with Grype
grype nginx:1.25
# Set severity threshold (for CI/CD)
grype nginx:1.25 --fail-on critical
3.4 CI/CD Integrated Scanning
# .github/workflows/image-scan.yml
name: Container Image Scan
on:
push:
branches: [main]
pull_request:
jobs:
build-and-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:ci .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:ci'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Run Grype scan
uses: anchore/scan-action@v4
with:
image: 'myapp:ci'
fail-build: true
severity-cutoff: 'high'
4. Image Signing: Sigstore and cosign
4.1 Sigstore Ecosystem
Sigstore Components:
┌──────────────────────────────────────────────────┐
│ cosign │
│ - Sign and verify container images/artifacts │
│ - Keyless signing support (OIDC-based) │
├──────────────────────────────────────────────────┤
│ Rekor │
│ - Immutable transparency log │
│ - All signatures stored publicly │
├──────────────────────────────────────────────────┤
│ Fulcio │
│ - Short-lived certificate issuance (CA) │
│ - Converts OIDC tokens to X.509 certificates │
└──────────────────────────────────────────────────┘
4.2 Signing Images with cosign
# Generate key pair
cosign generate-key-pair
# Sign image
cosign sign --key cosign.key myregistry.com/myapp:v1.0
# Verify image signature
cosign verify --key cosign.pub myregistry.com/myapp:v1.0
# Keyless signing (recommended for CI/CD)
# Auto-authenticates with OIDC token (GitHub Actions, GitLab CI)
cosign sign myregistry.com/myapp:v1.0
# Keyless verification
cosign verify \
--certificate-identity "https://github.com/myorg/myapp/.github/workflows/build.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
myregistry.com/myapp:v1.0
4.3 Keyless Signing in GitHub Actions
# .github/workflows/sign-image.yml
name: Build, Sign, and Push
on:
push:
tags: ['v*']
permissions:
contents: read
packages: write
id-token: write # Required for keyless signing
jobs:
build-sign-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: sigstore/cosign-installer@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: "${{ github.actor }}"
password: "${{ secrets.GITHUB_TOKEN }}"
- name: Build and push
id: build
uses: docker/build-push-action@v5
with:
push: true
tags: "ghcr.io/${{ github.repository }}:${{ github.ref_name }}"
- name: Sign the image (keyless)
run: |
cosign sign --yes \
"ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}"
4.4 Kubernetes Admission Controller for Signature Verification
# Kyverno policy: allow only signed images
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/myorg/*"
attestors:
- entries:
- keyless:
subject: "https://github.com/myorg/*/.github/workflows/*@refs/heads/main"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: "https://rekor.sigstore.dev"
5. SBOM (Software Bill of Materials)
5.1 SBOM Format Comparison
CycloneDX:
- OWASP project
- Security-focused (vulnerabilities, licenses)
- JSON, XML formats
- VEX (Vulnerability Exploitability eXchange) support
SPDX:
- Linux Foundation project
- License compliance focused
- JSON, RDF, Tag-Value formats
- ISO/IEC 5962:2021 international standard
5.2 Generating SBOM with syft
# Generate SBOM in CycloneDX format
syft packages myapp:latest -o cyclonedx-json > sbom-cyclonedx.json
# Generate SBOM in SPDX format
syft packages myapp:latest -o spdx-json > sbom-spdx.json
# Scan directory
syft dir:./my-project -o cyclonedx-json > project-sbom.json
# Attach SBOM to image (cosign + SBOM)
cosign attach sbom --sbom sbom-cyclonedx.json myregistry.com/myapp:v1.0
# Create SBOM attestation
cosign attest --predicate sbom-cyclonedx.json \
--type cyclonedx \
myregistry.com/myapp:v1.0
5.3 SBOM-Based Vulnerability Management
# Scan vulnerabilities from SBOM
grype sbom:./sbom-cyclonedx.json
# Query specific packages
syft packages myapp:latest | grep log4j
# License analysis
syft packages myapp:latest -o json | \
jq '.artifacts[].licenses[].value' | sort | uniq -c | sort -rn
6. Pod Security Standards
6.1 Security Level Comparison
Privileged:
- No restrictions
- Known privilege escalation allowed
- For system daemons, CNI plugins
Baseline:
- Prevents known privilege escalation
- Blocks hostNetwork, hostPID, hostIPC
- Blocks privileged containers
- Suitable for most workloads
Restricted:
- Least privilege principle applied
- Non-root enforced
- All capabilities dropped
- seccomp profile required
- For security-critical workloads
6.2 Pod Security Admission Configuration
# Apply Pod Security Standards to Namespace
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# Enforce Restricted level
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# Warn on Restricted violations
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
# Audit Restricted violations
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
6.3 Security-Hardened Pod Spec
# Pod spec satisfying Restricted level
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
# Security: disable auto-mount of SA token
automountServiceAccountToken: false
securityContext:
# Security: run as non-root group
runAsGroup: 1000
fsGroup: 1000
# Security: apply Seccomp profile
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: ghcr.io/myorg/secure-app:v1.0
ports:
- containerPort: 8080
protocol: TCP
securityContext:
# Security: run as non-root
runAsNonRoot: true
runAsUser: 1000
# Security: read-only root filesystem
readOnlyRootFilesystem: true
# Security: block privilege escalation
allowPrivilegeEscalation: false
# Security: drop all capabilities
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/cache
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: tmp
emptyDir:
sizeLimit: 100Mi
- name: cache
emptyDir:
sizeLimit: 200Mi
7. Network Policies
7.1 Default Deny Policy
# Block all ingress/egress (default deny)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
7.2 Fine-Grained Network Policies
# API service: allow ingress only from specific sources
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-service-policy
namespace: production
spec:
podSelector:
matchLabels:
app: api-service
policyTypes:
- Ingress
- Egress
ingress:
# Allow only from ingress controller
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
podSelector:
matchLabels:
app: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
# Allow database access
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
# Allow DNS
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
7.3 Cilium Network Policy
# Cilium L7 Network Policy (HTTP path-based)
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-l7-policy
namespace: production
spec:
endpointSelector:
matchLabels:
app: api-service
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: "GET"
path: "/api/v1/products"
- method: "POST"
path: "/api/v1/orders"
8. Runtime Security: Falco and Tetragon
8.1 Falco Rules
# Falco custom rules
- rule: Detect Reverse Shell
desc: Detect reverse shell connection attempts in containers
condition: >
evt.type=connect and
evt.dir=< and
container and
fd.typechar=4 and
fd.rip != "127.0.0.1" and
proc.name in (bash, sh, zsh, dash, csh)
output: >
Reverse shell detected (user=%user.name command=%proc.cmdline
connection=%fd.name container=%container.name
image=%container.image.repository)
priority: CRITICAL
tags: [network, shell, mitre_execution]
- rule: Detect Cryptomining
desc: Detect cryptocurrency mining processes in containers
condition: >
spawned_process and
container and
(proc.name in (xmrig, minerd, minergate, cpuminer) or
proc.cmdline contains "stratum+tcp" or
proc.cmdline contains "cryptonight")
output: >
Cryptomining detected (user=%user.name command=%proc.cmdline
container=%container.name image=%container.image.repository)
priority: CRITICAL
tags: [crypto, mining, mitre_resource_hijacking]
- rule: Sensitive File Access
desc: Detect access to sensitive files
condition: >
open_read and
container and
(fd.name startswith /etc/shadow or
fd.name startswith /etc/passwd or
fd.name startswith /proc/self/environ or
fd.name startswith /run/secrets)
output: >
Sensitive file access (user=%user.name file=%fd.name
command=%proc.cmdline container=%container.name)
priority: WARNING
tags: [filesystem, mitre_credential_access]
8.2 Falco Helm Deployment
# Install Falco
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
helm install falco falcosecurity/falco \
--namespace falco --create-namespace \
--set falcosidekick.enabled=true \
--set falcosidekick.config.slack.webhookurl="https://hooks.slack.com/services/xxx" \
--set falcosidekick.config.slack.minimumpriority=warning \
--set driver.kind=ebpf
8.3 Tetragon eBPF Policies
# Tetragon policy: process execution monitoring
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: monitor-process-execution
spec:
kprobes:
- call: "security_bprm_check"
syscall: false
args:
- index: 0
type: "linux_binprm"
selectors:
- matchBinaries:
- operator: "In"
values:
- "/bin/bash"
- "/bin/sh"
- "/usr/bin/curl"
- "/usr/bin/wget"
matchNamespaces:
- namespace: Mnt
operator: NotIn
values:
- "host_mnt_ns"
---
# Tetragon policy: network connection monitoring
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: monitor-network-connections
spec:
kprobes:
- call: "tcp_connect"
syscall: false
args:
- index: 0
type: "sock"
selectors:
- matchArgs:
- index: 0
operator: "NotDAddr"
values:
- "127.0.0.1"
- "10.0.0.0/8"
9. Secrets Management
9.1 External Secrets Operator
# SecretStore configuration (AWS Secrets Manager)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-2
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
# ExternalSecret definition
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/database
property: username
- secretKey: password
remoteRef:
key: prod/database
property: password
9.2 Vault CSI Provider
# Vault SecretProviderClass
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-db-creds
namespace: production
spec:
provider: vault
parameters:
vaultAddress: "https://vault.example.com"
roleName: "app-role"
objects: |
- objectName: "db-username"
secretPath: "secret/data/prod/database"
secretKey: "username"
- objectName: "db-password"
secretPath: "secret/data/prod/database"
secretKey: "password"
---
# Using in Pod
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
serviceAccountName: app-sa
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: secrets
mountPath: "/mnt/secrets"
readOnly: true
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: vault-db-creds
10. SLSA Framework
10.1 SLSA Levels
SLSA Level 1 - Provenance:
- Build process documented
- Automated build
SLSA Level 2 - Build Service:
- Hosted build service
- Signed provenance generated
- Version-controlled source
SLSA Level 3 - Build Integrity:
- Isolated build environment
- Tamper-resistant provenance
- Source integrity guaranteed
SLSA Level 4 - Dependencies:
- Recursive assurance for all dependencies
- Reproducible builds
- (Currently theoretical)
10.2 Generating SLSA Provenance
# SLSA Provenance generation in GitHub Actions
name: SLSA Build
on:
push:
tags: ['v*']
permissions:
contents: read
packages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: "${{ steps.build.outputs.digest }}"
steps:
- uses: actions/checkout@v4
- name: Build and push
id: build
uses: docker/build-push-action@v5
with:
push: true
tags: "ghcr.io/${{ github.repository }}:${{ github.ref_name }}"
provenance:
needs: build
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with:
image: "ghcr.io/${{ github.repository }}"
digest: "${{ needs.build.outputs.digest }}"
secrets:
registry-username: "${{ github.actor }}"
registry-password: "${{ secrets.GITHUB_TOKEN }}"
11. CI/CD Security Pipeline
11.1 Complete Pipeline Architecture
Security CI/CD Pipeline:
┌─────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│Build│ -> │ Scan │ -> │ Sign │ -> │Attest│ -> │Verify│ -> │Deploy│
└─────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘
| | | | | |
Build Vuln cosign SBOM Kyverno Signature
image scan signing attestation policy verified
Trivy SLSA check deploy
Grype provenance
11.2 CIS Benchmarks and Kubescape
# kube-bench: run CIS Kubernetes Benchmark
kube-bench run --targets master,node
# Kubescape: NSA, MITRE, CIS framework scan
kubescape scan framework cis-v1.23 --exclude-namespaces kube-system
# Scan specific control
kubescape scan control C-0002 --namespace production
# Kubescape YAML scan
kubescape scan .
12. Quiz
Test your understanding of container security covered in this article.
Q1. Image Security
Question: What is the difference between Distroless images and Alpine images?
Answer: Distroless images are minimal images without a shell (bash/sh) or package manager. Alpine is a lightweight Linux distribution based on musl libc that includes a shell and package manager (apk).
Distroless has a smaller attack surface but is harder to debug. Alpine allows debugging but may have musl libc compatibility issues. For production, Distroless or Chainguard images are recommended, using debug-tagged variants when debugging is needed.
Q2. Sigstore Keyless Signing
Question: Why is cosign keyless signing preferred over key-based signing in CI/CD?
Answer: Keyless signing uses OIDC tokens (e.g., GitHub Actions workflow ID tokens), eliminating the need to manage and protect long-lived private keys.
Fulcio converts OIDC tokens to short-lived certificates, and signatures are recorded in the Rekor transparency log. This eliminates key rotation, key leakage, and key storage concerns, significantly reducing operational burden in CI/CD environments.
Q3. Pod Security Standards
Question: List three security requirements that must be met at the Restricted level of Pod Security Standards.
Answer:
- Run as non-root user (runAsNonRoot: true)
- Drop all Linux capabilities (capabilities.drop: ALL)
- Block privilege escalation (allowPrivilegeEscalation: false)
Additionally, applying a seccomp profile (RuntimeDefault or Localhost) and read-only root filesystem (readOnlyRootFilesystem: true) are also recommended at the Restricted level.
Q4. Runtime Security
Question: Explain the architectural differences between Falco and Tetragon.
Answer:
- Falco: Captures system calls via a kernel module or eBPF driver, analyzing events in a userspace rules engine. Detection-focused, generating alerts when rules match.
- Tetragon: Pure eBPF-based, executing policies directly at the kernel level. Supports both detection and enforcement, directly controlling process execution, network connections, and file access at the kernel level.
Q5. SLSA Framework
Question: What are the two core security properties required at SLSA Level 3?
Answer:
- Isolated build environment: Builds run in an environment isolated from other builds, preventing cross-contamination.
- Tamper-resistant provenance: The provenance generated by the build system cannot be modified by the build itself. Provenance is signed by the build service, ensuring the integrity of both source and build process.
13. References
- Trivy - https://aquasecurity.github.io/trivy/
- Sigstore (cosign, Rekor, Fulcio) - https://www.sigstore.dev/
- SLSA Framework - https://slsa.dev/
- Falco - https://falco.org/docs/
- Tetragon - https://tetragon.io/docs/
- Grype - https://github.com/anchore/grype
- syft - https://github.com/anchore/syft
- CycloneDX - https://cyclonedx.org/
- SPDX - https://spdx.dev/
- Kyverno - https://kyverno.io/docs/
- External Secrets Operator - https://external-secrets.io/
- Kubescape - https://kubescape.io/
- Chainguard Images - https://www.chainguard.dev/chainguard-images
- CIS Benchmarks - https://www.cisecurity.org/benchmark/kubernetes
This article covered the entire lifecycle of container security: image security (minimal base images, scanning), signing and verification (Sigstore/cosign), SBOM, Pod Security Standards, Network Policies, runtime security (Falco/Tetragon), secrets management, the SLSA framework, and CI/CD security pipelines. Adopting a Defense in Depth strategy and integrating security from development through CI/CD pipelines is key.