spinny:~/writing $ vim devsecops-shift-left-security-guide.md
1~2코드를 작성하는 중에 발견된 취약점은 수정하는 데 몇 분이면 됩니다. 같은 취약점이 프로덕션에서 발견되면 한 스프린트를 소모합니다. 그리고 공격자가 먼저 발견하면 수백만 달러의 비용이 발생합니다. 이것이 **shift-left 보안**의 핵심 논거입니다 - 보안 검사를 개발 생명주기의 가능한 한 초기 단계로 앞당기는 것입니다.3~4DevSecOps는 이 아이디어를 실천으로 전환합니다: 보안은 마지막에 있는 별도의 단계가 아니라, 코드의 첫 줄부터 프로덕션 배포까지 개발의 모든 단계에 녹아든 지속적인 프로세스입니다.5~6## 늦은 보안의 비용7~8IBM의 데이터 침해 비용 보고서는 보안 문제의 수정 비용이 발견 시점이 늦어질수록 기하급수적으로 증가한다는 것을 지속적으로 보여주고 있습니다:9~10| 단계 | 수정 비용 | 수정 시간 |11|-------|------------|-------------|12| **IDE / 로컬 개발** | 몇 분 | 몇 초에서 몇 분 |13| **코드 리뷰 / PR** | 몇 시간 | 몇 분에서 몇 시간 |14| **CI/CD 파이프라인** | 며칠 | 몇 시간에서 며칠 |15| **스테이징 / QA** | 몇 주 | 며칠 |16| **프로덕션** | 몇 달 | 몇 주에서 몇 달 |17| **침해 후** | 수백만 달러 | 몇 달에서 수 년 |18~19결론은 명확합니다: 보안을 한 단계 앞당길 때마다 비용과 시간이 한 자릿수씩 절감됩니다.20~21## DevSecOps 파이프라인22~23성숙한 DevSecOps 파이프라인은 모든 단계에 보안 검사를 통합합니다:24~25```mermaid26graph LR27 IDE[IDE / Editor] --> PC[Pre-commit Hooks]28 PC --> PR[Pull Request]29 PR --> CI[CI Pipeline]30 CI --> Build[Build / Package]31 Build --> Deploy[Deploy]32 Deploy --> Runtime[Runtime / Production]33~34 IDE -.- S1[SAST\nSecret Detection\nLinting]35 PC -.- S2[Secrets Scan\nFormat Check]36 PR -.- S3[Code Review\nDependency Audit]37 CI -.- S4[SAST\nSCA\nContainer Scan\nSBOM]38 Build -.- S5[Image Signing\nArtifact Verification]39 Deploy -.- S6[Policy Enforcement\nAdmission Control]40 Runtime -.- S7[DAST\nWAF\nMonitoring]41```42~43각 단계를 구체적인 도구와 설정으로 살펴보겠습니다.44~45## 단계 1: IDE에서의 보안46~47가장 빠른 피드백 루프입니다. 파일을 저장하기도 전에 취약점을 잡아냅니다.48~49### 추천 도구50~51- **Semgrep**: OWASP 취약점에 대한 커뮤니티 규칙을 갖춘 경량 정적 분석 도구52- **Snyk IDE Extension**: 실시간 의존성 취약점 스캐닝53- **GitLens + GitLeaks**: 에디터에서 secret 감지54- **ESLint Security Plugins**: Node.js용 `eslint-plugin-security`, DOM XSS용 `eslint-plugin-no-unsanitized`55~56### 예시: ESLint 보안 설정57~58```json59{60 "extends": ["eslint:recommended"],61 "plugins": ["security", "no-unsanitized"],62 "rules": {63 "security/detect-object-injection": "warn",64 "security/detect-non-literal-regexp": "warn",65 "security/detect-unsafe-regex": "error",66 "security/detect-buffer-noassert": "error",67 "security/detect-eval-with-expression": "error",68 "security/detect-no-csrf-before-method-override": "error",69 "security/detect-possible-timing-attacks": "warn",70 "no-unsanitized/method": "error",71 "no-unsanitized/property": "error"72 }73}74```75~76## 단계 2: Pre-commit Hooks77~78두 번째 방어선입니다. 모든 commit 전에 자동으로 실행되어 위험한 코드가 저장소에 들어가는 것을 차단합니다.79~80### Gitleaks: Secret이 Git에 들어가기 전에 잡기81~82코드베이스에서 가장 흔한 보안 실수는 secret을 commit하는 것입니다 - API 키, 데이터베이스 비밀번호, 토큰 등. secret이 git 히스토리에 한 번 들어가면 완전히 제거하기가 매우 어렵습니다(force push를 해도 fork와 캐시에 남아있을 수 있습니다).83~84```yaml85# .pre-commit-config.yaml86repos:87 - repo: https://github.com/gitleaks/gitleaks88 rev: v8.21.089 hooks:90 - id: gitleaks91~92 - repo: https://github.com/semgrep/semgrep93 rev: v1.90.094 hooks:95 - id: semgrep96 args: ['--config', 'auto']97```98~99설치 및 활성화:100~101```bash102pip install pre-commit103pre-commit install104```105~106이제 `git commit`할 때마다 유출된 secret과 일반적인 취약점이 자동으로 스캔됩니다. 무언가 발견되면 commit이 차단됩니다.107~108### 커스텀 Gitleaks 규칙109~110조직 고유의 secret에 대한 커스텀 패턴을 추가할 수 있습니다:111~112```toml113# .gitleaks.toml114title = "Custom Gitleaks Config"115~116[[rules]]117id = "internal-api-key"118description = "Internal API key detected"119regex = '''INTERNAL_KEY_[A-Za-z0-9]{32}'''120tags = ["key", "internal"]121```122~123## 단계 3: CI/CD 파이프라인 보안124~125본격적인 작업이 이루어지는 곳입니다. CI 파이프라인은 모든 pull request에 대해 여러 보안 스캔을 실행해야 합니다.126~127### SAST (Static Application Security Testing)128~129SAST 도구는 소스 코드를 실행하지 않고 분석하여 취약점을 나타내는 패턴을 찾습니다.130~131```yaml132# .github/workflows/security.yml133name: Security Scan134on:135 pull_request:136 branches: [main]137~138jobs:139 sast:140 name: Static Analysis141 runs-on: ubuntu-latest142 steps:143 - uses: actions/checkout@v4144~145 - name: Run Semgrep146 uses: semgrep/semgrep-action@v1147 with:148 config: >-149 p/owasp-top-ten150 p/typescript151 p/nodejs152 p/react153 generateSarif: true154~155 - name: Upload SARIF156 uses: github/codeql-action/upload-sarif@v3157 with:158 sarif_file: semgrep.sarif159```160~161Semgrep의 `p/owasp-top-ten` 규칙 세트는 가장 일반적인 취약점을 잡아냅니다: SQL injection, XSS, SSRF, path traversal, insecure deserialization 등.162~163### SCA (Software Composition Analysis)164~165SCA는 의존성에서 알려진 취약점을 스캔합니다. 이것은 매우 중요합니다 - 현대 애플리케이션 코드의 80% 이상이 오픈 소스 의존성에서 옵니다.166~167```yaml168 dependency-scan:169 name: Dependency Audit170 runs-on: ubuntu-latest171 steps:172 - uses: actions/checkout@v4173~174 - name: Run Snyk175 uses: snyk/actions/node@master176 env:177 SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}178 with:179 args: --severity-threshold=high180~181 - name: npm audit182 run: npm audit --audit-level=high183```184~185### Trivy를 사용한 컨테이너 보안186~187Docker 이미지를 빌드한다면 취약점 스캔은 필수입니다. **Trivy**는 가장 인기 있는 오픈 소스 컨테이너 스캐너입니다.188~189```yaml190 container-scan:191 name: Container Security192 runs-on: ubuntu-latest193 steps:194 - uses: actions/checkout@v4195~196 - name: Build image197 run: docker build -t my-app:${{ github.sha }} .198~199 - name: Run Trivy200 uses: aquasecurity/trivy-action@master201 with:202 image-ref: my-app:${{ github.sha }}203 format: 'sarif'204 output: 'trivy-results.sarif'205 severity: 'CRITICAL,HIGH'206 exit-code: '1'207~208 - name: Upload Trivy SARIF209 uses: github/codeql-action/upload-sarif@v3210 with:211 sarif_file: trivy-results.sarif212```213~214### SBOM 생성215~216**Software Bill of Materials (SBOM)** 는 애플리케이션 내 모든 구성 요소의 완전한 목록입니다. 컴플라이언스 프레임워크와 정부 규정에서 점점 더 요구되고 있습니다(미국 사이버 보안 행정명령은 연방 소프트웨어에 SBOM을 의무화하고 있습니다).217~218```yaml219 sbom:220 name: Generate SBOM221 runs-on: ubuntu-latest222 steps:223 - uses: actions/checkout@v4224~225 - name: Generate SBOM with Syft226 uses: anchore/sbom-action@v0227 with:228 format: spdx-json229 output-file: sbom.spdx.json230~231 - name: Upload SBOM232 uses: actions/upload-artifact@v4233 with:234 name: sbom235 path: sbom.spdx.json236```237~238## 단계 4: 안전한 Docker 이미지239~240프로덕션 Docker 이미지는 최소 권한 원칙을 따라야 합니다. 강화된 Dockerfile의 예시입니다:241~242```dockerfile243# Build stage244FROM node:22-alpine AS builder245WORKDIR /app246COPY package.json package-lock.json ./247RUN npm ci248COPY . .249RUN npm run build250~251# Production stage252FROM node:22-alpine AS runner253WORKDIR /app254~255# Install dumb-init before dropping root256RUN apk add --no-cache dumb-init257~258# Don't run as root259RUN addgroup -S app && adduser -S app -G app260~261# Copy only what's needed262COPY --from=builder --chown=app:app /app/dist ./dist263COPY --from=builder --chown=app:app /app/node_modules ./node_modules264COPY --from=builder --chown=app:app /app/package.json ./265~266# Drop to non-root user267USER app268ENTRYPOINT ["dumb-init", "--"]269~270# Health check271HEALTHCHECK --interval=30s --timeout=3s --retries=3 \272 CMD wget -qO- http://localhost:3000/health || exit 1273~274EXPOSE 3000275CMD ["node", "dist/server.js"]276```277~278핵심 실천 사항:279~2801. **멀티 스테이지 빌드 사용**: builder 단계에는 개발 의존성, runner 단계에는 프로덕션 코드만2812. **root로 실행하지 않기**: 비root 사용자를 생성하고 전환2823. **Alpine 이미지 사용**: 더 작은 공격 표면(기본 설치 패키지가 적음)2834. **이미지 버전 고정**: 공급망 공격을 방지하기 위해 `node:latest` 대신 `node:22-alpine` 사용2845. **`npm ci` 사용**: lock 파일에서 결정적 설치, `npm install`이 아닌285~286## 단계 5: Secret 관리287~288하드코딩된 secret은 개발자가 유발한 사고에서 침해의 가장 큰 원인입니다.289~290### 하지 말아야 할 것291~292```typescript293// NEVER do this294const API_KEY = "sk-1234567890abcdef";295const DB_PASSWORD = "supersecret123";296~297const client = new Client({298 connectionString: `postgres://admin:${DB_PASSWORD}@db.example.com/prod`299});300```301~302### 대신 해야 할 것303~304```typescript305// Use environment variables306const client = new Client({307 connectionString: process.env.DATABASE_URL308});309~310// Or use a secret manager311import { SecretManagerServiceClient } from '@google-cloud/secret-manager';312~313const client = new SecretManagerServiceClient();314const [version] = await client.accessSecretVersion({315 name: 'projects/my-project/secrets/db-password/versions/latest',316});317const dbPassword = version.payload?.data?.toString();318```319~320### Secret 관리 계층321~322```mermaid323graph TD324 A[Secret Manager\nAWS Secrets Manager\nGCP Secret Manager\nHashiCorp Vault] --> B[Best: Rotated, audited, centralized]325 C[Environment Variables\nInjected at deploy time] --> D[Good: Not in code, but static]326 E[.env files\nWith .gitignore] --> F[Acceptable: Local development only]327 G[Hard-coded in source] --> H[NEVER: Instant breach risk]328~329 style A fill:#d4edda330 style C fill:#fff3cd331 style E fill:#ffeeba332 style G fill:#f8d7da333```334~335## OWASP Top 10: 빠른 참조336~337모든 개발자가 OWASP Top 10을 알아야 합니다. 요약 버전:338~339| # | 취약점 | 설명 | 예방 |340|---|--------------|-----------|------------|341| 1 | **Broken Access Control** | 사용자가 접근해서는 안 되는 리소스에 접근 | 기본적으로 거부, 서버 측에서 검증 |342| 2 | **Cryptographic Failures** | 약한 암호화, 평문 데이터 | 강력한 알고리즘(AES-256, bcrypt) 사용, 모든 곳에 TLS |343| 3 | **Injection** | SQL, NoSQL, OS 명령 주입 | 파라미터화된 쿼리, 입력 검증 |344| 4 | **Insecure Design** | 결함 있는 아키텍처 | 위협 모델링, 보안 설계 패턴 |345| 5 | **Security Misconfiguration** | 기본 자격 증명, 공개된 클라우드 버킷 | 강화된 기본값, 자동화된 설정 감사 |346| 6 | **Vulnerable Components** | 의존성의 알려진 CVE | SCA 스캔, 정기적인 업데이트 |347| 7 | **Auth Failures** | 약한 비밀번호, 손상된 세션 | MFA, 속도 제한, 안전한 세션 관리 |348| 8 | **Data Integrity Failures** | 서명되지 않은 업데이트, 신뢰할 수 없는 CI/CD | 코드 서명, SBOM, 파이프라인 무결성 |349| 9 | **Logging Failures** | 감사 추적 없음 | 구조화된 로깅, 이상 징후 알림 |350| 10 | **SSRF** | Server-side request forgery | 아웃바운드 URL 허용 목록, 입력 검증 |351~352## 완전한 GitHub Actions 보안 워크플로우353~354위의 모든 것을 결합한 프로덕션 준비 워크플로우:355~356```yaml357# .github/workflows/security.yml358name: Security Pipeline359on:360 pull_request:361 branches: [main]362 push:363 branches: [main]364~365permissions:366 contents: read367 security-events: write368~369jobs:370 secrets-scan:371 name: Secret Detection372 runs-on: ubuntu-latest373 steps:374 - uses: actions/checkout@v4375 with:376 fetch-depth: 0377 - uses: gitleaks/gitleaks-action@v2378 env:379 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}380~381 sast:382 name: Static Analysis (SAST)383 runs-on: ubuntu-latest384 steps:385 - uses: actions/checkout@v4386 - uses: semgrep/semgrep-action@v1387 with:388 config: p/owasp-top-ten p/typescript p/nodejs389~390 dependency-audit:391 name: Dependency Scan (SCA)392 runs-on: ubuntu-latest393 steps:394 - uses: actions/checkout@v4395 - uses: actions/setup-node@v4396 with:397 node-version: 22398 - run: npm ci399 - run: npm audit --audit-level=high400 - uses: snyk/actions/node@master401 env:402 SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}403 with:404 args: --severity-threshold=high405 continue-on-error: true406~407 container-scan:408 name: Container Scan409 runs-on: ubuntu-latest410 needs: [sast, dependency-audit]411 steps:412 - uses: actions/checkout@v4413 - run: docker build -t app:${{ github.sha }} .414 - uses: aquasecurity/trivy-action@master415 with:416 image-ref: app:${{ github.sha }}417 severity: CRITICAL,HIGH418 exit-code: '1'419~420 sbom:421 name: SBOM Generation422 runs-on: ubuntu-latest423 needs: [container-scan]424 steps:425 - uses: actions/checkout@v4426 - uses: anchore/sbom-action@v0427 with:428 format: spdx-json429 output-file: sbom.spdx.json430```431~432```mermaid433graph TD434 PR[Pull Request] --> S1[Secret Detection]435 PR --> S2[SAST - Semgrep]436 PR --> S3[SCA - Snyk + npm audit]437 S2 --> S4[Container Scan - Trivy]438 S3 --> S4439 S4 --> S5[SBOM Generation]440 S5 --> Deploy[Deploy]441~442 S1 -.- F1[Block if secrets found]443 S2 -.- F2[Block on critical vulns]444 S3 -.- F3[Block on high severity]445 S4 -.- F4[Block on critical CVEs]446```447~448## 추적해야 할 지표449~450DevSecOps 프로그램이 효과적인지 어떻게 알 수 있을까요? 다음 지표를 추적하세요:451~452- **평균 복구 시간 (MTTR)**: 탐지 후 취약점을 수정하는 속도453- **취약점 탈출률**: 프로덕션에 도달한 취약점의 비율454- **오탐률**: 오탐이 너무 많으면 알림 피로와 경고 무시로 이어짐455- **의존성 최신도**: 의존성의 평균 나이(오래될수록 알려진 CVE가 있을 가능성이 높음)456- **SBOM 커버리지**: 최신 SBOM을 보유한 프로젝트의 비율457~458## 시작하기: 실용적인 로드맵459~460모든 것을 한 번에 구현하려고 하지 마세요. 단계적 접근이 더 효과적입니다:461~462```mermaid463flowchart TD464 A[Week 1-2: Foundations] --> B[Week 3-4: CI/CD Integration]465 B --> C[Month 2: Container Security]466 C --> D[Month 3+: Advanced]467~468 A --> A1[Add Gitleaks pre-commit hooks]469 A --> A2[Enable npm audit in CI]470 A --> A3[Add .gitignore for .env files]471~472 B --> B1[Add Semgrep to GitHub Actions]473 B --> B2[Add Snyk dependency scanning]474 B --> B3[Set up SARIF upload to GitHub]475~476 C --> C1[Add Trivy container scanning]477 C --> C2[Harden Dockerfiles]478 C --> C3[Generate SBOMs]479~480 D --> D1[Secret manager integration]481 D --> D2[Runtime protection - DAST]482 D --> D3[Policy as code - OPA]483```484~485## 결론486~487DevSecOps는 파이프라인에 더 많은 도구를 추가하는 것이 아닙니다 - 보안을 소프트웨어를 만드는 자연스러운 과정의 일부로 만드는 것입니다. 목표는 모든 PR을 보안 경고로 차단하는 것이 아니라, 코드가 아직 머릿속에 생생할 때 문제를 수정할 수 있도록 개발자에게 빠른 피드백을 제공하는 것입니다.488~489기본부터 시작하세요: secret을 위한 pre-commit hooks, CI에서의 의존성 스캔, Docker 이미지의 컨테이너 스캔. 그리고 팀의 필요에 따라 반복적으로 개선해 나가세요.490~491보안은 한 번 배포하는 기능이 아닙니다. 모든 commit에 녹여 넣는 실천입니다.492~493> **DevSecOps 시작 체크리스트:**494>495> - [x] Gitleaks pre-commit hooks 설치496> - [x] .env와 secret 파일을 .gitignore에 추가497> - [x] CI 파이프라인에 Semgrep SAST 통합498> - [x] Snyk 또는 npm audit로 의존성 스캔499> - [x] Trivy로 컨테이너 이미지 스캔500> - [x] Dockerfile에서 비root 사용자 사용501> - [x] 환경 변수 또는 secret manager에 secret 저장502> - [x] 모든 릴리스에서 SBOM 생성503> - [x] 팀 전체의 OWASP Top 10 인지504~
NORMAL · devsecops-shift-left-security-guide.md [readonly]504 lines · :q to close