- Published on
DevSecOps 완전 가이드 2025: Shift-Left 보안, SAST/DAST/SCA, 컨테이너 보안까지
- Authors

- Name
- Youngju Kim
- @fjvbn20031
TL;DR
- Shift-Left 보안: 개발 초기에 보안을 적용하면 결함 수정 비용이 30배 절감
- SAST: SonarQube, Semgrep, CodeQL로 코드 레벨 취약점 탐지
- DAST: OWASP ZAP, Burp Suite로 런타임 취약점 발견
- SCA: Snyk, Dependabot로 의존성 취약점 자동 패치
- 컨테이너 보안: Trivy, Grype로 이미지 스캔, 최소 베이스 이미지 사용
- 공급망 보안: SBOM 생성, SLSA 레벨 달성, Sigstore 서명
- Secret 탐지: GitLeaks, TruffleHog로 커밋된 비밀정보 탐지
- GitHub Actions: 통합 보안 파이프라인으로 모든 단계 자동화
목차
- DevSecOps란 무엇인가
- Shift-Left 보안 전략
- SAST - 정적 분석
- DAST - 동적 분석
- SCA - 소프트웨어 구성 분석
- 컨테이너 보안
- Secret 탐지
- 공급망 보안 - SBOM과 SLSA
- Policy-as-Code
- GitHub Actions 통합 보안 파이프라인
- 실전 퀴즈
- 참고 자료
1. DevSecOps란 무엇인가
1.1 전통적 보안의 한계
전통적인 소프트웨어 개발에서 보안은 마지막 단계에 추가되었습니다. 개발이 끝나고 배포 직전에 보안 팀이 검토하는 방식이었죠. 이 접근법의 문제점은 명확합니다.
| 문제 | 영향 |
|---|---|
| 늦은 발견 | 수정 비용 30배 증가 |
| 병목 현상 | 보안 팀이 모든 프로젝트 검토 |
| 개발자 무관심 | "보안은 보안 팀의 일" 마인드 |
| 릴리즈 지연 | 취약점 발견 시 일정 전체 지연 |
| 컨텍스트 부족 | 보안 팀이 코드 의도를 모름 |
1.2 DevSecOps의 핵심 원칙
DevSecOps는 Development + Security + Operations의 합성어로, 보안을 소프트웨어 개발 생명 주기(SDLC) 전체에 통합하는 문화이자 실천 방법입니다.
핵심 원칙 5가지:
- 모든 사람이 보안에 책임: 보안은 보안 팀만의 일이 아님
- 자동화 우선: 수동 검토 대신 자동화된 도구 활용
- 빠른 피드백: 개발자가 코드를 작성하는 시점에 보안 피드백
- 지속적 개선: 보안 메트릭 측정과 지속적인 개선
- 공유 책임: 개발, 운영, 보안 팀 간 지식 공유
1.3 DevSecOps 도구 생태계 개요
┌─────────────────────────────────────────────────────────────┐
│ DevSecOps 파이프라인 │
├──────────┬──────────┬──────────┬──────────┬────────────────┤
│ Plan │ Code │ Build │ Test │ Deploy/Run │
├──────────┼──────────┼──────────┼──────────┼────────────────┤
│ Threat │ SAST │ SCA │ DAST │ Runtime │
│ Modeling │ Semgrep │ Snyk │ ZAP │ Protection │
│ │ CodeQL │ Trivy │ Burp │ Falco │
│ Security │ SonarQube│ OSV │ Nuclei │ Container │
│ Require │ │ │ │ Security │
│ ments │ Secret │ SBOM │ Fuzzing │ Network │
│ │ Detect │ Signing │ │ Policy │
├──────────┼──────────┼──────────┼──────────┼────────────────┤
│ Policy-as-Code (OPA / Kyverno / Conftest) │
└─────────────────────────────────────────────────────────────┘
2. Shift-Left 보안 전략
2.1 Shift-Left이란?
Shift-Left은 보안 활동을 개발 프로세스의 왼쪽(초기 단계)으로 이동시키는 전략입니다.
전통적 방식:
Plan → Code → Build → Test → Deploy → [보안 검토] → Release
↑ 여기서 발견 = 비용 높음
Shift-Left 방식:
Plan → [보안] → Code → [보안] → Build → [보안] → Test → Deploy
↑ 여기서 시작 = 비용 낮음
2.2 Shift-Left ROI 분석
IBM Systems Sciences Institute의 연구에 따르면, 결함 수정 비용은 발견 시점에 따라 기하급수적으로 증가합니다.
| 발견 단계 | 상대 비용 | 예시 |
|---|---|---|
| 요구사항 정의 | 1x | 보안 요구사항 누락 발견 |
| 설계 | 5x | 아키텍처 취약점 발견 |
| 구현 | 10x | 코드 레벨 취약점 발견 |
| 테스트 | 20x | QA 단계에서 발견 |
| 운영 | 30x | 프로덕션에서 발견 |
| 사고 발생 후 | 100x+ | 데이터 유출 사고 |
2.3 Shift-Left 구현 단계
단계 1: 보안 챔피언 프로그램
각 개발 팀에 보안 챔피언을 지정합니다.
# security-champions.yaml
teams:
- name: "Backend Team"
champion: "alice@company.com"
responsibilities:
- "PR 보안 리뷰"
- "SAST 결과 트리아지"
- "월간 보안 교육 진행"
training:
- "OWASP Top 10"
- "Secure Coding Practices"
- "Threat Modeling 기초"
- name: "Frontend Team"
champion: "bob@company.com"
responsibilities:
- "XSS/CSRF 방지 검토"
- "의존성 취약점 관리"
- "CSP 헤더 관리"
단계 2: 위협 모델링
STRIDE 모델을 활용한 위협 모델링을 설계 단계에서 수행합니다.
STRIDE 위협 모델:
┌─────────────────────────────────────────┐
│ S - Spoofing (위장) │
│ T - Tampering (변조) │
│ R - Repudiation (부인) │
│ I - Information Disclosure (정보 노출) │
│ D - Denial of Service (서비스 거부) │
│ E - Elevation of Privilege (권한 상승) │
└─────────────────────────────────────────┘
단계 3: IDE 보안 플러그인
개발자의 IDE에서 바로 보안 피드백을 제공합니다.
// .vscode/settings.json
{
"semgrep.scan.autoScan": true,
"semgrep.scan.configuration": [
"p/owasp-top-ten",
"p/typescript",
"p/react"
],
"sonarlint.connectedMode.project": {
"connectionId": "my-sonarqube",
"projectKey": "my-project"
}
}
3. SAST - 정적 분석
3.1 SAST란?
SAST(Static Application Security Testing)는 소스 코드, 바이트코드, 바이너리를 실행하지 않고 분석하여 보안 취약점을 찾는 기법입니다.
SAST가 탐지하는 주요 취약점:
- SQL Injection
- Cross-Site Scripting (XSS)
- 경로 탐색(Path Traversal)
- 하드코딩된 비밀정보
- 안전하지 않은 암호화
- 버퍼 오버플로우
3.2 SonarQube 설정
# docker-compose.yml - SonarQube 로컬 설정
version: "3.8"
services:
sonarqube:
image: sonarqube:community
ports:
- "9000:9000"
environment:
SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonarqube
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: sonar
volumes:
- sonar_data:/opt/sonarqube/data
- sonar_logs:/opt/sonarqube/logs
- sonar_extensions:/opt/sonarqube/extensions
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_DB: sonarqube
POSTGRES_USER: sonar
POSTGRES_PASSWORD: sonar
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
sonar_data:
sonar_logs:
sonar_extensions:
postgres_data:
# sonar-project.properties
sonar.projectKey=my-app
sonar.projectName=My Application
sonar.projectVersion=1.0
sonar.sources=src
sonar.tests=tests
sonar.language=ts
sonar.sourceEncoding=UTF-8
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.qualitygate.wait=true
3.3 Semgrep 실전 활용
Semgrep은 가볍고 빠른 SAST 도구로, 커스텀 룰 작성이 쉽습니다.
# .semgrep.yml - 커스텀 보안 룰
rules:
- id: no-eval-usage
patterns:
- pattern: eval(...)
message: "eval() 사용은 코드 인젝션 위험이 있습니다"
severity: ERROR
languages: [javascript, typescript]
metadata:
owasp: "A03:2021 Injection"
cwe: "CWE-95"
- id: no-innerHTML
patterns:
- pattern: $EL.innerHTML = $VAL
message: "innerHTML 직접 설정은 XSS 위험이 있습니다. textContent 또는 DOMPurify를 사용하세요"
severity: WARNING
languages: [javascript, typescript]
- id: sql-injection-risk
patterns:
- pattern: |
$DB.query(`... ${$VAR} ...`)
- pattern: |
$DB.query("..." + $VAR + "...")
message: "SQL 인젝션 위험! 파라미터 바인딩을 사용하세요"
severity: ERROR
languages: [javascript, typescript]
fix: |
$DB.query("... $1 ...", [$VAR])
- id: no-hardcoded-secrets
patterns:
- pattern: |
const $KEY = "AKIA..."
- pattern: |
const $KEY = "sk-..."
message: "하드코딩된 시크릿이 발견되었습니다. 환경 변수를 사용하세요"
severity: ERROR
languages: [javascript, typescript]
# Semgrep 실행
semgrep --config auto --config .semgrep.yml src/
# 특정 룰셋만 실행
semgrep --config p/owasp-top-ten src/
semgrep --config p/typescript src/
semgrep --config p/react src/
# CI용 JSON 출력
semgrep --config auto --json --output results.json src/
3.4 CodeQL 고급 쿼리
GitHub CodeQL은 코드를 데이터베이스로 변환하여 쿼리하는 강력한 도구입니다.
// codeql-queries/sql-injection.ql
/**
* @name SQL injection in query string
* @description 사용자 입력이 SQL 쿼리에 직접 포함됨
* @kind path-problem
* @problem.severity error
* @security-severity 9.8
* @precision high
* @id js/sql-injection
* @tags security
* external/cwe/cwe-089
*/
import javascript
import DataFlow::PathGraph
class SqlInjectionConfig extends TaintTracking::Configuration {
SqlInjectionConfig() { this = "SqlInjectionConfig" }
override predicate isSource(DataFlow::Node source) {
exists(Express::RequestInputAccess input |
source = input
)
}
override predicate isSink(DataFlow::Node sink) {
exists(DatabaseAccess query |
sink = query.getAQueryArgument()
)
}
}
from SqlInjectionConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"이 쿼리는 $@에서 받은 사용자 입력을 포함합니다.",
source.getNode(), "사용자 입력"
3.5 SAST 도구 비교
| 기능 | SonarQube | Semgrep | CodeQL |
|---|---|---|---|
| 가격 | Community 무료 | OSS 무료 | OSS 무료 |
| 지원 언어 | 30+ | 30+ | 10+ |
| 커스텀 룰 | Java DSL | YAML | QL Language |
| CI 통합 | 좋음 | 매우 좋음 | GitHub 최적화 |
| 속도 | 보통 | 매우 빠름 | 느림 (DB 빌드) |
| 정확도 | 높음 | 높음 | 매우 높음 |
| 학습 곡선 | 보통 | 낮음 | 높음 |
4. DAST - 동적 분석
4.1 DAST란?
DAST(Dynamic Application Security Testing)는 실행 중인 애플리케이션에 실제 공격을 시뮬레이션하여 취약점을 탐지합니다. 소스 코드가 필요 없으므로 "블랙박스 테스팅"이라고도 합니다.
4.2 OWASP ZAP 자동화
# zap-automation.yaml
env:
contexts:
- name: "My Web App"
urls:
- "https://staging.example.com"
includePaths:
- "https://staging.example.com/.*"
excludePaths:
- "https://staging.example.com/logout"
authentication:
method: "form"
parameters:
loginUrl: "https://staging.example.com/login"
loginRequestData: "username=test&password=test123"
jobs:
- type: spider
parameters:
context: "My Web App"
maxDuration: 5
maxDepth: 10
- type: spiderAjax
parameters:
context: "My Web App"
maxDuration: 5
- type: passiveScan-wait
parameters:
maxDuration: 10
- type: activeScan
parameters:
context: "My Web App"
maxRuleDurationInMins: 5
policy: "API-Scan"
- type: report
parameters:
template: "modern"
reportDir: "/zap/reports"
reportFile: "zap-report"
reportTitle: "ZAP Security Report"
risks:
- high
- medium
- low
# Docker로 ZAP 자동화 스캔 실행
docker run --rm -v $(pwd):/zap/wrk:rw \
-t zaproxy/zap-stable \
zap.sh -cmd -autorun /zap/wrk/zap-automation.yaml
# API 스캔 (OpenAPI 스펙 기반)
docker run --rm -v $(pwd):/zap/wrk:rw \
-t zaproxy/zap-stable \
zap-api-scan.py \
-t https://staging.example.com/openapi.json \
-f openapi \
-r api-report.html
4.3 DAST와 SAST 비교
| 특성 | SAST | DAST |
|---|---|---|
| 분석 시점 | 빌드 시 | 런타임 |
| 소스 코드 필요 | 예 | 아니오 |
| 실행 환경 필요 | 아니오 | 예 |
| 오탐율 | 높음 | 낮음 |
| 커버리지 | 전체 코드 | 도달 가능한 경로 |
| 취약점 유형 | 코드 레벨 | 런타임/설정 |
| 피드백 속도 | 빠름 | 느림 |
5. SCA - 소프트웨어 구성 분석
5.1 왜 SCA가 중요한가?
현대 애플리케이션의 80% 이상이 오픈소스 코드로 구성됩니다. Log4Shell(CVE-2021-44228)은 단일 오픈소스 라이브러리의 취약점이 전 세계적 영향을 미칠 수 있음을 보여주었습니다.
5.2 Snyk 설정
# .snyk - Snyk 정책 파일
version: v1.5
ignore:
SNYK-JS-LODASH-590103:
- '*':
reason: "해당 함수를 사용하지 않음"
expires: "2026-06-01T00:00:00.000Z"
patch: {}
language-settings:
javascript:
severity-threshold: high
# Snyk CLI 사용법
snyk test # 취약점 검사
snyk monitor # 지속적 모니터링 등록
snyk test --severity-threshold=high # 높은 심각도만
snyk code test # SAST 스캔
snyk container test myimage:latest # 컨테이너 스캔
snyk iac test terraform/ # IaC 스캔
5.3 Dependabot 설정
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "Asia/Seoul"
open-pull-requests-limit: 10
reviewers:
- "security-team"
labels:
- "dependencies"
- "security"
commit-message:
prefix: "deps"
include: "scope"
# 보안 업데이트는 자동 머지
groups:
security-patches:
patterns:
- "*"
update-types:
- "security"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
labels:
- "docker"
- "dependencies"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "ci"
- "dependencies"
5.4 Renovate 고급 설정
// renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":semanticCommits",
"group:allNonMajor",
"schedule:weekends"
],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"],
"schedule": ["at any time"]
},
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true
},
{
"matchPackagePatterns": ["eslint", "prettier"],
"groupName": "linting tools",
"automerge": true
},
{
"matchPackagePatterns": ["^@types/"],
"groupName": "type definitions",
"automerge": true
}
],
"prConcurrentLimit": 5,
"prHourlyLimit": 2
}
6. 컨테이너 보안
6.1 컨테이너 보안의 중요성
컨테이너 이미지에는 OS 패키지, 런타임, 애플리케이션 의존성이 모두 포함됩니다. 하나의 이미지에 수백 개의 취약점이 있을 수 있습니다.
6.2 Trivy 이미지 스캔
# 기본 이미지 스캔
trivy image myapp:latest
# 심각도 필터링
trivy image --severity CRITICAL,HIGH myapp:latest
# 수정 가능한 취약점만
trivy image --ignore-unfixed myapp:latest
# SBOM 생성
trivy image --format cyclonedx --output sbom.json myapp:latest
# CI용 JSON 출력과 종료 코드
trivy image --format json --output results.json \
--exit-code 1 --severity CRITICAL myapp:latest
# 파일시스템 스캔 (빌드 전)
trivy fs --security-checks vuln,secret,config .
# Kubernetes 매니페스트 스캔
trivy config k8s/
6.3 안전한 Dockerfile 작성
# BAD - 보안 안티패턴
FROM ubuntu:latest
RUN apt-get update && apt-get install -y python3
COPY . /app
RUN pip install -r requirements.txt
USER root
EXPOSE 8080
CMD ["python3", "app.py"]
# GOOD - 보안 모범 사례
# 1. 최소 베이스 이미지 사용
FROM python:3.12-slim AS builder
# 2. 패키지 설치 후 캐시 정리
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc && \
rm -rf /var/lib/apt/lists/*
# 3. 별도 빌드 스테이지
WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 4. 최종 이미지는 최소화
FROM python:3.12-slim
# 5. 비루트 사용자
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 6. 빌드 아티팩트만 복사
COPY /install /usr/local
COPY . /app
WORKDIR /app
USER appuser
# 7. 읽기 전용 파일시스템 지원
EXPOSE 8080
HEALTHCHECK \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["python3", "app.py"]
6.4 Grype와 Docker Scout
# Grype - 이미지 취약점 스캔
grype myapp:latest
grype myapp:latest --only-fixed
grype myapp:latest -o json > grype-results.json
# Docker Scout - Docker Desktop 통합
docker scout cves myapp:latest
docker scout recommendations myapp:latest
docker scout quickview myapp:latest
6.5 컨테이너 보안 체크리스트
컨테이너 보안 체크리스트:
이미지 빌드:
[x] 최소 베이스 이미지 (distroless, alpine, slim)
[x] 멀티스테이지 빌드
[x] 비루트 사용자 실행
[x] 불필요한 패키지 제거
[x] .dockerignore 설정
[x] 이미지 서명 (cosign)
런타임:
[x] 읽기 전용 파일시스템
[x] 리소스 제한 (CPU, 메모리)
[x] capabilities 최소화
[x] seccomp/AppArmor 프로파일
[x] 네트워크 정책 적용
레지스트리:
[x] 프라이빗 레지스트리 사용
[x] 이미지 취약점 스캔
[x] 태그 불변성 설정
[x] 이미지 서명 검증
7. Secret 탐지
7.1 유출되는 Secret의 현실
GitHub에 따르면 매년 수백만 건의 시크릿이 공개 리포지토리에 커밋됩니다. API 키, 패스워드, 인증서가 유출되면 몇 분 안에 악용될 수 있습니다.
7.2 GitLeaks 설정
# .gitleaks.toml
title = "Custom Gitleaks Configuration"
[extend]
useDefault = true
[[rules]]
id = "custom-api-key"
description = "Custom API Key Pattern"
regex = '''(?i)api[_-]?key\s*[:=]\s*['"][a-zA-Z0-9]{32,}['"]'''
entropy = 3.5
secretGroup = 0
tags = ["api", "key"]
[[rules]]
id = "private-key-file"
description = "Private Key File Reference"
regex = '''(?i)(private[_-]?key|ssh[_-]?key)\s*[:=]\s*['"].*\.(pem|key|p12)['"]'''
tags = ["key", "private"]
[allowlist]
description = "Global Allowlist"
paths = [
'''\.gitleaks\.toml''',
'''test/.*''',
'''.*_test\.go''',
'''.*\.test\.(ts|js)'''
]
regexes = [
'''EXAMPLE_.*''',
'''REPLACE_ME'''
]
# GitLeaks 실행
gitleaks detect --source . --verbose
gitleaks detect --source . --report-format json --report-path leaks.json
# Git 히스토리 전체 스캔
gitleaks detect --source . --log-opts="--all"
# pre-commit hook
gitleaks protect --staged --verbose
7.3 TruffleHog 활용
# 리포지토리 전체 스캔
trufflehog git file://. --only-verified
# GitHub 조직 전체 스캔
trufflehog github --org=my-org --only-verified
# Docker 이미지 스캔
trufflehog docker --image=myapp:latest
# S3 버킷 스캔
trufflehog s3 --bucket=my-bucket
7.4 pre-commit Hook 설정
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: detect-private-key
- id: check-added-large-files
args: ['--maxkb=500']
- repo: https://github.com/returntocorp/semgrep
rev: v1.50.0
hooks:
- id: semgrep
args: ['--config', 'p/secrets', '--error']
8. 공급망 보안 - SBOM과 SLSA
8.1 공급망 공격 사례
| 사건 | 연도 | 영향 |
|---|---|---|
| SolarWinds | 2020 | 18,000+ 기관 피해 |
| Codecov | 2021 | CI 스크립트 변조 |
| ua-parser-js | 2021 | npm 패키지 하이재킹 |
| Log4Shell | 2021 | 전 세계 Java 앱 영향 |
| xz utils | 2024 | Linux 백도어 시도 |
8.2 SBOM 생성
SBOM(Software Bill of Materials)은 소프트웨어에 포함된 모든 구성요소의 목록입니다.
# CycloneDX 포맷 SBOM 생성
# npm 프로젝트
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
# Python 프로젝트
cyclonedx-py requirements requirements.txt -o sbom.json
# 컨테이너 이미지
trivy image --format cyclonedx -o sbom.json myapp:latest
syft myapp:latest -o cyclonedx-json > sbom.json
# SPDX 포맷
syft myapp:latest -o spdx-json > sbom-spdx.json
// SBOM 예시 (CycloneDX 간략)
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"metadata": {
"component": {
"type": "application",
"name": "my-web-app",
"version": "2.1.0"
}
},
"components": [
{
"type": "library",
"name": "express",
"version": "4.18.2",
"purl": "pkg:npm/express@4.18.2",
"licenses": [{ "license": { "id": "MIT" } }]
},
{
"type": "library",
"name": "lodash",
"version": "4.17.21",
"purl": "pkg:npm/lodash@4.17.21"
}
]
}
8.3 SLSA 프레임워크
SLSA(Supply-chain Levels for Software Artifacts)는 공급망 보안의 성숙도를 측정하는 프레임워크입니다.
SLSA 레벨:
┌──────────────┬─────────────────────────────────────┐
│ SLSA Level 0 │ 보안 보장 없음 │
│ SLSA Level 1 │ 빌드 프로세스 문서화 (provenance) │
│ SLSA Level 2 │ 호스팅된 빌드, 서명된 provenance │
│ SLSA Level 3 │ 격리된 빌드, 변조 방지 │
└──────────────┴─────────────────────────────────────┘
8.4 Sigstore로 아티팩트 서명
# cosign으로 컨테이너 이미지 서명
cosign sign --key cosign.key myregistry/myapp:v1.0.0
# 키리스 서명 (OIDC 기반)
cosign sign myregistry/myapp:v1.0.0
# 서명 검증
cosign verify --key cosign.pub myregistry/myapp:v1.0.0
# SBOM 첨부
cosign attach sbom --sbom sbom.json myregistry/myapp:v1.0.0
# 아티팩트 증명 (attestation) 생성
cosign attest --predicate provenance.json \
--type slsaprovenance \
myregistry/myapp:v1.0.0
9. Policy-as-Code
9.1 OPA (Open Policy Agent)
# policy/kubernetes.rego
package kubernetes.admission
# 루트 컨테이너 금지
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
container.securityContext.runAsUser == 0
msg := sprintf("컨테이너 '%s'가 root로 실행됩니다", [container.name])
}
# 최신 태그 금지
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("컨테이너 '%s'가 latest 태그를 사용합니다", [container.name])
}
# 리소스 제한 필수
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not container.resources.limits
msg := sprintf("컨테이너 '%s'에 리소스 제한이 없습니다", [container.name])
}
# 읽기 전용 루트 파일시스템 필수
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not container.securityContext.readOnlyRootFilesystem
msg := sprintf("컨테이너 '%s'가 읽기 전용 파일시스템을 사용하지 않습니다", [container.name])
}
9.2 Kyverno 정책
# kyverno-policies.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
validationFailureAction: Enforce
rules:
- name: require-team-label
match:
any:
- resources:
kinds:
- Pod
validate:
message: "모든 Pod에는 'team' 라벨이 필요합니다"
pattern:
metadata:
labels:
team: "?*"
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged
spec:
validationFailureAction: Enforce
rules:
- name: no-privileged-containers
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Privileged 컨테이너는 허용되지 않습니다"
pattern:
spec:
containers:
- securityContext:
privileged: "false"
9.3 Conftest로 IaC 검증
# Conftest로 Terraform 검증
conftest test terraform/ --policy policy/
# Dockerfile 검증
conftest test Dockerfile --policy policy/docker/
# Kubernetes 매니페스트 검증
conftest test k8s/ --policy policy/kubernetes/
# policy/docker/dockerfile.rego
package main
deny[msg] {
input[i].Cmd == "from"
val := input[i].Value
contains(val[0], ":latest")
msg := "FROM에 latest 태그 사용 금지"
}
deny[msg] {
input[i].Cmd == "user"
val := input[i].Value
val[0] == "root"
msg := "USER root 사용 금지"
}
10. GitHub Actions 통합 보안 파이프라인
10.1 종합 보안 워크플로우
# .github/workflows/security.yml
name: Security Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1' # 매주 월요일 오전 6시
permissions:
contents: read
security-events: write
pull-requests: write
jobs:
# 1단계: Secret 탐지
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: GitLeaks Scan
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 2단계: SAST
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep Scan
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/owasp-top-ten
p/typescript
p/react
generateSarif: "1"
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
if: always()
# 3단계: SCA (의존성 스캔)
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Snyk Security Scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
# 4단계: 컨테이너 보안
container-security:
runs-on: ubuntu-latest
needs: [sast]
steps:
- uses: actions/checkout@v4
- name: Build Docker Image
run: docker build -t myapp:test .
- name: Trivy Image Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:test'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
if: always()
# 5단계: SBOM 생성
sbom:
runs-on: ubuntu-latest
needs: [container-security]
steps:
- uses: actions/checkout@v4
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myapp:test
format: cyclonedx-json
output-file: sbom.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.json
# 6단계: IaC 스캔
iac-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Trivy IaC Scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'config'
scan-ref: '.'
format: 'sarif'
output: 'trivy-iac.sarif'
- name: Upload IaC SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-iac.sarif'
if: always()
# 7단계: DAST (스테이징 배포 후)
dast:
runs-on: ubuntu-latest
needs: [container-security]
if: github.ref == 'refs/heads/main'
steps:
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.10.0
with:
target: 'https://staging.example.com'
rules_file_name: '.zap-rules.tsv'
fail_action: 'warn'
# 최종: 보안 게이트
security-gate:
runs-on: ubuntu-latest
needs: [secret-scan, sast, sca, container-security, iac-security]
steps:
- name: Security Gate Check
run: |
echo "All security checks passed!"
echo "Pipeline completed at $(date)"
10.2 GitHub Advanced Security 활용
# .github/workflows/codeql.yml
name: CodeQL Analysis
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 4 * * 1'
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
security-events: write
strategy:
matrix:
language: ['javascript', 'typescript']
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: +security-extended,security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
10.3 보안 파이프라인 메트릭
성공적인 DevSecOps 프로그램에서 추적해야 하는 주요 메트릭입니다.
| 메트릭 | 설명 | 목표 |
|---|---|---|
| MTTR | 취약점 평균 수정 시간 | Critical: 24시간 이내 |
| 취약점 밀도 | 코드 1000줄당 취약점 수 | 0.5 이하 |
| 스캔 커버리지 | 보안 스캔 적용된 리포지토리 비율 | 100% |
| 오탐율 | 전체 발견 중 오탐 비율 | 20% 이하 |
| 파이프라인 통과율 | 보안 게이트 통과 비율 | 85% 이상 |
| 의존성 최신도 | 최신 버전 의존성 비율 | 80% 이상 |
11. 실전 퀴즈
Q1: SAST와 DAST의 차이점은 무엇인가요?
정답:
- SAST는 소스 코드를 정적으로 분석하여 취약점을 찾습니다. 빌드 단계에서 실행되며 실행 환경이 필요 없습니다.
- DAST는 실행 중인 애플리케이션에 공격을 시뮬레이션합니다. 런타임 환경이 필요하며 소스 코드 없이도 가능합니다.
- SAST는 오탐율이 높지만 전체 코드를 분석할 수 있고, DAST는 오탐율이 낮지만 도달 가능한 경로만 분석합니다.
- 둘 다 사용하는 것이 이상적이며, 이를 IAST(Interactive AST)라고도 합니다.
Q2: Shift-Left 보안이 비용을 절감하는 이유는?
정답:
- 결함 수정 비용은 발견 시점이 늦을수록 기하급수적으로 증가합니다.
- 요구사항 단계에서 발견하면 1x, 설계 5x, 구현 10x, 테스트 20x, 운영 30x, 사고 후 100x+ 비용이 듭니다.
- Shift-Left은 보안을 개발 초기(왼쪽)로 이동시켜 결함을 빨리 발견하므로 비용이 절감됩니다.
- 또한 개발자가 코드를 작성하는 시점에 피드백을 받으므로 컨텍스트 전환 비용도 줄어듭니다.
Q3: SBOM이 공급망 보안에서 중요한 이유는?
정답:
- SBOM은 소프트웨어에 포함된 모든 구성요소(라이브러리, 프레임워크 등)의 목록입니다.
- Log4Shell 같은 취약점이 발견되면 SBOM으로 영향받는 시스템을 즉시 식별할 수 있습니다.
- 라이선스 컴플라이언스 확인에도 필수적입니다.
- 미국 행정명령(EO 14028)에서 정부 소프트웨어 공급업체에 SBOM 제출을 의무화했습니다.
- CycloneDX와 SPDX가 대표적인 SBOM 표준 포맷입니다.
Q4: 안전한 컨테이너 이미지를 만들기 위한 모범 사례 5가지는?
정답:
- 최소 베이스 이미지: distroless, alpine, slim 이미지 사용으로 공격 표면 축소
- 멀티스테이지 빌드: 빌드 도구를 최종 이미지에 포함시키지 않음
- 비루트 사용자: USER 명령으로 루트가 아닌 사용자로 실행
- 이미지 서명: cosign으로 이미지 서명 및 검증
- 정기적 스캔: Trivy/Grype로 이미지 취약점 정기 스캔
추가 사항: 읽기 전용 파일시스템, capabilities 최소화, 불필요한 패키지 제거
Q5: GitHub Actions에서 보안 파이프라인을 구성할 때 실행 순서는?
정답:
권장 순서:
- Secret 탐지 (GitLeaks) - 가장 빠르고 중요
- SAST (Semgrep/CodeQL) - 코드 레벨 취약점
- SCA (Snyk/Dependabot) - 의존성 취약점
- IaC 스캔 (Trivy/Conftest) - 인프라 설정 검증
- 컨테이너 보안 (Trivy) - 이미지 빌드 후 스캔
- SBOM 생성 - 아티팩트 목록 생성
- DAST (ZAP) - 스테이징 배포 후 런타임 테스트
- 보안 게이트 - 모든 결과 종합 판단
Secret 탐지와 SAST는 병렬 실행 가능하며, DAST는 배포 후에만 실행됩니다.
12. 참고 자료
- OWASP DevSecOps Guideline
- NIST Secure Software Development Framework (SSDF)
- SLSA - Supply-chain Levels for Software Artifacts
- Sigstore - Software Signing
- Semgrep Registry
- GitHub Security Lab
- Trivy Documentation
- Snyk Learn - Security Education
- OWASP Top 10 (2021)
- CycloneDX SBOM Standard
- OPA - Open Policy Agent
- Kyverno Documentation
- CISA SBOM Resources
- Google SLSA GitHub Generator