코드를 작성하는 중에 발견된 취약점은 수정하는 데 몇 분이면 됩니다. 같은 취약점이 프로덕션에서 발견되면 한 스프린트를 소모합니다. 그리고 공격자가 먼저 발견하면 수백만 달러의 비용이 발생합니다. 이것이 shift-left 보안의 핵심 논거입니다 - 보안 검사를 개발 생명주기의 가능한 한 초기 단계로 앞당기는 것입니다.
DevSecOps는 이 아이디어를 실천으로 전환합니다: 보안은 마지막에 있는 별도의 단계가 아니라, 코드의 첫 줄부터 프로덕션 배포까지 개발의 모든 단계에 녹아든 지속적인 프로세스입니다.
늦은 보안의 비용
IBM의 데이터 침해 비용 보고서는 보안 문제의 수정 비용이 발견 시점이 늦어질수록 기하급수적으로 증가한다는 것을 지속적으로 보여주고 있습니다:
| 단계 | 수정 비용 | 수정 시간 |
|---|---|---|
| IDE / 로컬 개발 | 몇 분 | 몇 초에서 몇 분 |
| 코드 리뷰 / PR | 몇 시간 | 몇 분에서 몇 시간 |
| CI/CD 파이프라인 | 며칠 | 몇 시간에서 며칠 |
| 스테이징 / QA | 몇 주 | 며칠 |
| 프로덕션 | 몇 달 | 몇 주에서 몇 달 |
| 침해 후 | 수백만 달러 | 몇 달에서 수 년 |
결론은 명확합니다: 보안을 한 단계 앞당길 때마다 비용과 시간이 한 자릿수씩 절감됩니다.
DevSecOps 파이프라인
성숙한 DevSecOps 파이프라인은 모든 단계에 보안 검사를 통합합니다:
각 단계를 구체적인 도구와 설정으로 살펴보겠습니다.
단계 1: IDE에서의 보안
가장 빠른 피드백 루프입니다. 파일을 저장하기도 전에 취약점을 잡아냅니다.
추천 도구
- Semgrep: OWASP 취약점에 대한 커뮤니티 규칙을 갖춘 경량 정적 분석 도구
- Snyk IDE Extension: 실시간 의존성 취약점 스캐닝
- GitLens + GitLeaks: 에디터에서 secret 감지
- ESLint Security Plugins: Node.js용
eslint-plugin-security, DOM XSS용eslint-plugin-no-unsanitized
예시: ESLint 보안 설정
{ "extends": ["eslint:recommended"], "plugins": ["security", "no-unsanitized"], "rules": { "security/detect-object-injection": "warn", "security/detect-non-literal-regexp": "warn", "security/detect-unsafe-regex": "error", "security/detect-buffer-noassert": "error", "security/detect-eval-with-expression": "error", "security/detect-no-csrf-before-method-override": "error", "security/detect-possible-timing-attacks": "warn", "no-unsanitized/method": "error", "no-unsanitized/property": "error" } }
단계 2: Pre-commit Hooks
두 번째 방어선입니다. 모든 commit 전에 자동으로 실행되어 위험한 코드가 저장소에 들어가는 것을 차단합니다.
Gitleaks: Secret이 Git에 들어가기 전에 잡기
코드베이스에서 가장 흔한 보안 실수는 secret을 commit하는 것입니다 - API 키, 데이터베이스 비밀번호, 토큰 등. secret이 git 히스토리에 한 번 들어가면 완전히 제거하기가 매우 어렵습니다(force push를 해도 fork와 캐시에 남아있을 수 있습니다).
# .pre-commit-config.yaml repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.21.0 hooks: - id: gitleaks - repo: https://github.com/semgrep/semgrep rev: v1.90.0 hooks: - id: semgrep args: ['--config', 'auto']
설치 및 활성화:
pip install pre-commit pre-commit install
이제 git commit할 때마다 유출된 secret과 일반적인 취약점이 자동으로 스캔됩니다. 무언가 발견되면 commit이 차단됩니다.
커스텀 Gitleaks 규칙
조직 고유의 secret에 대한 커스텀 패턴을 추가할 수 있습니다:
# .gitleaks.toml title = "Custom Gitleaks Config" [[rules]] id = "internal-api-key" description = "Internal API key detected" regex = '''INTERNAL_KEY_[A-Za-z0-9]{32}''' tags = ["key", "internal"]
단계 3: CI/CD 파이프라인 보안
본격적인 작업이 이루어지는 곳입니다. CI 파이프라인은 모든 pull request에 대해 여러 보안 스캔을 실행해야 합니다.
SAST (Static Application Security Testing)
SAST 도구는 소스 코드를 실행하지 않고 분석하여 취약점을 나타내는 패턴을 찾습니다.
# .github/workflows/security.yml name: Security Scan on: pull_request: branches: [main] jobs: sast: name: Static Analysis runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Semgrep uses: semgrep/semgrep-action@v1 with: config: >- p/owasp-top-ten p/typescript p/nodejs p/react generateSarif: true - name: Upload SARIF uses: github/codeql-action/upload-sarif@v3 with: sarif_file: semgrep.sarif
Semgrep의 p/owasp-top-ten 규칙 세트는 가장 일반적인 취약점을 잡아냅니다: SQL injection, XSS, SSRF, path traversal, insecure deserialization 등.
SCA (Software Composition Analysis)
SCA는 의존성에서 알려진 취약점을 스캔합니다. 이것은 매우 중요합니다 - 현대 애플리케이션 코드의 80% 이상이 오픈 소스 의존성에서 옵니다.
dependency-scan: name: Dependency Audit runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Snyk uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --severity-threshold=high - name: npm audit run: npm audit --audit-level=high
Trivy를 사용한 컨테이너 보안
Docker 이미지를 빌드한다면 취약점 스캔은 필수입니다. Trivy는 가장 인기 있는 오픈 소스 컨테이너 스캐너입니다.
container-scan: name: Container Security runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build image run: docker build -t my-app:${{ github.sha }} . - name: Run Trivy uses: aquasecurity/trivy-action@master with: image-ref: my-app:${{ github.sha }} 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
SBOM 생성
Software Bill of Materials (SBOM) 는 애플리케이션 내 모든 구성 요소의 완전한 목록입니다. 컴플라이언스 프레임워크와 정부 규정에서 점점 더 요구되고 있습니다(미국 사이버 보안 행정명령은 연방 소프트웨어에 SBOM을 의무화하고 있습니다).
sbom: name: Generate SBOM runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Generate SBOM with Syft uses: anchore/sbom-action@v0 with: format: spdx-json output-file: sbom.spdx.json - name: Upload SBOM uses: actions/upload-artifact@v4 with: name: sbom path: sbom.spdx.json
단계 4: 안전한 Docker 이미지
프로덕션 Docker 이미지는 최소 권한 원칙을 따라야 합니다. 강화된 Dockerfile의 예시입니다:
# Build stage FROM node:22-alpine AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build # Production stage FROM node:22-alpine AS runner WORKDIR /app # Install dumb-init before dropping root RUN apk add --no-cache dumb-init # Don't run as root RUN addgroup -S app && adduser -S app -G app # Copy only what's needed COPY /app/dist ./dist COPY /app/node_modules ./node_modules COPY /app/package.json ./ # Drop to non-root user USER app ENTRYPOINT ["dumb-init", "--"] # Health check HEALTHCHECK \ CMD wget -qO- http://localhost:3000/health || exit 1 EXPOSE 3000 CMD ["node", "dist/server.js"]
핵심 실천 사항:
- 멀티 스테이지 빌드 사용: builder 단계에는 개발 의존성, runner 단계에는 프로덕션 코드만
- root로 실행하지 않기: 비root 사용자를 생성하고 전환
- Alpine 이미지 사용: 더 작은 공격 표면(기본 설치 패키지가 적음)
- 이미지 버전 고정: 공급망 공격을 방지하기 위해
node:latest대신node:22-alpine사용 npm ci사용: lock 파일에서 결정적 설치,npm install이 아닌
단계 5: Secret 관리
하드코딩된 secret은 개발자가 유발한 사고에서 침해의 가장 큰 원인입니다.
하지 말아야 할 것
// NEVER do this const API_KEY = "sk-1234567890abcdef"; const DB_PASSWORD = "supersecret123"; const client = new Client({ connectionString: `postgres://admin:${DB_PASSWORD}@db.example.com/prod` });
대신 해야 할 것
// Use environment variables const client = new Client({ connectionString: process.env.DATABASE_URL }); // Or use a secret manager import { SecretManagerServiceClient } from '@google-cloud/secret-manager'; const client = new SecretManagerServiceClient(); const [version] = await client.accessSecretVersion({ name: 'projects/my-project/secrets/db-password/versions/latest', }); const dbPassword = version.payload?.data?.toString();
Secret 관리 계층
OWASP Top 10: 빠른 참조
모든 개발자가 OWASP Top 10을 알아야 합니다. 요약 버전:
| # | 취약점 | 설명 | 예방 |
|---|---|---|---|
| 1 | Broken Access Control | 사용자가 접근해서는 안 되는 리소스에 접근 | 기본적으로 거부, 서버 측에서 검증 |
| 2 | Cryptographic Failures | 약한 암호화, 평문 데이터 | 강력한 알고리즘(AES-256, bcrypt) 사용, 모든 곳에 TLS |
| 3 | Injection | SQL, NoSQL, OS 명령 주입 | 파라미터화된 쿼리, 입력 검증 |
| 4 | Insecure Design | 결함 있는 아키텍처 | 위협 모델링, 보안 설계 패턴 |
| 5 | Security Misconfiguration | 기본 자격 증명, 공개된 클라우드 버킷 | 강화된 기본값, 자동화된 설정 감사 |
| 6 | Vulnerable Components | 의존성의 알려진 CVE | SCA 스캔, 정기적인 업데이트 |
| 7 | Auth Failures | 약한 비밀번호, 손상된 세션 | MFA, 속도 제한, 안전한 세션 관리 |
| 8 | Data Integrity Failures | 서명되지 않은 업데이트, 신뢰할 수 없는 CI/CD | 코드 서명, SBOM, 파이프라인 무결성 |
| 9 | Logging Failures | 감사 추적 없음 | 구조화된 로깅, 이상 징후 알림 |
| 10 | SSRF | Server-side request forgery | 아웃바운드 URL 허용 목록, 입력 검증 |
완전한 GitHub Actions 보안 워크플로우
위의 모든 것을 결합한 프로덕션 준비 워크플로우:
# .github/workflows/security.yml name: Security Pipeline on: pull_request: branches: [main] push: branches: [main] permissions: contents: read security-events: write jobs: secrets-scan: name: Secret Detection runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} sast: name: Static Analysis (SAST) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: semgrep/semgrep-action@v1 with: config: p/owasp-top-ten p/typescript p/nodejs dependency-audit: name: Dependency Scan (SCA) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npm audit --audit-level=high - uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --severity-threshold=high continue-on-error: true container-scan: name: Container Scan runs-on: ubuntu-latest needs: [sast, dependency-audit] steps: - uses: actions/checkout@v4 - run: docker build -t app:${{ github.sha }} . - uses: aquasecurity/trivy-action@master with: image-ref: app:${{ github.sha }} severity: CRITICAL,HIGH exit-code: '1' sbom: name: SBOM Generation runs-on: ubuntu-latest needs: [container-scan] steps: - uses: actions/checkout@v4 - uses: anchore/sbom-action@v0 with: format: spdx-json output-file: sbom.spdx.json
추적해야 할 지표
DevSecOps 프로그램이 효과적인지 어떻게 알 수 있을까요? 다음 지표를 추적하세요:
- 평균 복구 시간 (MTTR): 탐지 후 취약점을 수정하는 속도
- 취약점 탈출률: 프로덕션에 도달한 취약점의 비율
- 오탐률: 오탐이 너무 많으면 알림 피로와 경고 무시로 이어짐
- 의존성 최신도: 의존성의 평균 나이(오래될수록 알려진 CVE가 있을 가능성이 높음)
- SBOM 커버리지: 최신 SBOM을 보유한 프로젝트의 비율
시작하기: 실용적인 로드맵
모든 것을 한 번에 구현하려고 하지 마세요. 단계적 접근이 더 효과적입니다:
결론
DevSecOps는 파이프라인에 더 많은 도구를 추가하는 것이 아닙니다 - 보안을 소프트웨어를 만드는 자연스러운 과정의 일부로 만드는 것입니다. 목표는 모든 PR을 보안 경고로 차단하는 것이 아니라, 코드가 아직 머릿속에 생생할 때 문제를 수정할 수 있도록 개발자에게 빠른 피드백을 제공하는 것입니다.
기본부터 시작하세요: secret을 위한 pre-commit hooks, CI에서의 의존성 스캔, Docker 이미지의 컨테이너 스캔. 그리고 팀의 필요에 따라 반복적으로 개선해 나가세요.
보안은 한 번 배포하는 기능이 아닙니다. 모든 commit에 녹여 넣는 실천입니다.
DevSecOps 시작 체크리스트:
- Gitleaks pre-commit hooks 설치
- .env와 secret 파일을 .gitignore에 추가
- CI 파이프라인에 Semgrep SAST 통합
- Snyk 또는 npm audit로 의존성 스캔
- Trivy로 컨테이너 이미지 스캔
- Dockerfile에서 비root 사용자 사용
- 환경 변수 또는 secret manager에 secret 저장
- 모든 릴리스에서 SBOM 생성
- 팀 전체의 OWASP Top 10 인지