GitHub Actions CI/CD 파이프라인 보안 취약점 자동 탐지 설정 실전 가이드

GitHub Actions CI/CD 파이프라인 보안 취약점 자동 탐지 설정 실전 가이드

PR을 머지한 직후 프로덕션에서 보안 취약점이 발견된 경험이 있다면, 그건 파이프라인에 보안 게이트가 없었기 때문입니다. 코드 리뷰에서 기능 버그는 잡을 수 있어도, 의존성 라이브러리의 CVE나 하드코딩된 시크릿은 사람 눈으로 잡기가 거의 불가능합니다.

GitHub Actions CI/CD 파이프라인 보안 취약점 자동 탐지는 이 문제를 코드가 레포지토리에 들어오는 순간부터 차단합니다. CodeQL로 소스코드 취약점을 분석하고, Trivy로 컨테이너 이미지와 의존성을 스캔하며, Dependabot으로 취약한 라이브러리 버전을 자동으로 업데이트하는 3단계 구조가 핵심입니다.

이 글을 읽고 나면: GitHub Actions 워크플로우 파일 3개를 레포지토리에 추가하는 것만으로 코드 푸시 시 보안 취약점이 자동 탐지·리포트되는 DevSecOps 파이프라인을 완성할 수 있습니다.

GitHub Actions 보안 파이프라인 전체 아키텍처

보안 스캔은 CI 파이프라인의 어느 단계에 배치하느냐에 따라 효과가 달라집니다. 너무 앞에 두면 불필요한 스캔이 많아지고, 너무 뒤에 두면 취약한 코드가 이미 빌드된 이후라 롤백 비용이 커집니다.

graph LR
    A[git push / PR] --> B[코드 스캔 CodeQL]
    B --> C{취약점 발견?}
    C -->|Yes| D[PR 차단 + 알림]
    C -->|No| E[의존성 스캔 Trivy]
    E --> F{CVE 발견?}
    F -->|Yes| G[심각도별 처리]
    F -->|No| H[컨테이너 빌드]
    H --> I[이미지 스캔 Trivy]
    I --> J[배포 승인 게이트]

권장 배치 전략은 다음과 같습니다.

스캔 단계 도구 트리거 실패 시 처리
소스코드 분석 CodeQL PR 생성, push to main PR 머지 차단
의존성 취약점 Trivy (fs mode) PR 생성 CRITICAL만 차단
컨테이너 이미지 Trivy (image mode) 이미지 빌드 후 CRITICAL 차단
자동 패치 Dependabot 주 1회 스케줄 PR 자동 생성

CodeQL로 소스코드 정적 분석 자동화하기

CodeQL은 GitHub가 제공하는 정적 분석 엔진으로, 공개 레포지토리에서는 완전 무료입니다. SQL 인젝션, XSS, 경로 순회 같은 OWASP Top 10 취약점 클래스를 코드 수준에서 탐지합니다.

.github/workflows/codeql.yml을 아래와 같이 작성합니다.

# GitHub Actions CodeQL 정적 분석 워크플로우
name: CodeQL Security Analysis

on:
  push:
    branches: [ "main", "develop" ]
  pull_request:
    branches: [ "main" ]
  schedule:
    # 매주 월요일 오전 9시(UTC) 전체 스캔
    - cron: '0 0 * * 1'

jobs:
  analyze:
    name: Analyze (${{ matrix.language }})
    runs-on: ubuntu-latest
    permissions:
      # 보안 결과를 GitHub Security 탭에 업로드하기 위한 권한
      security-events: write
      actions: read
      contents: read

    strategy:
      fail-fast: false
      matrix:
        # 프로젝트 언어에 맞게 수정 (python, javascript, java, go 등)
        language: [ 'python', 'javascript' ]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          # 추가 쿼리: 보안 관련 확장 쿼리 포함
          queries: security-extended

      - name: Autobuild
        uses: github/codeql-action/autobuild@v3

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3
        with:
          category: "/language:${{matrix.language}}"

설정 후 레포지토리의 Security → Code scanning 탭에서 결과를 확인할 수 있습니다. 발견된 취약점은 심각도(Critical, High, Medium, Low)로 분류되어 표시됩니다.

실무 팁: 처음 CodeQL을 도입하면 기존 코드에서 수십 개의 알림이 쏟아질 수 있습니다. 전부 즉시 수정하려 하지 말고, Critical과 High만 먼저 처리하고 나머지는 Issue로 등록해 점진적으로 해결하는 방식이 현실적입니다.

Trivy로 의존성 및 컨테이너 이미지 취약점 스캔하기

Trivy는 Aqua Security가 만든 오픈소스 취약점 스캐너입니다. 파일시스템, Git 레포지토리, 컨테이너 이미지를 모두 스캔할 수 있어 단일 도구로 의존성 CVE와 이미지 취약점을 동시에 커버합니다.

.github/workflows/trivy.yml을 작성합니다.

# Trivy 취약점 스캔 워크플로우 (의존성 + 컨테이너 이미지)
name: Trivy Vulnerability Scan

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  trivy-fs-scan:
    name: Filesystem Dependency Scan
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run Trivy filesystem scan
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          # CRITICAL, HIGH 취약점 발견 시 빌드 실패
          severity: 'CRITICAL,HIGH'
          # SARIF 형식으로 결과 저장 (GitHub Security 탭 연동)
          format: 'sarif'
          output: 'trivy-fs-results.sarif'
          # 수정 가능한 취약점만 보고 (노이즈 감소)
          ignore-unfixed: true

      - name: Upload Trivy scan results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: 'trivy-fs-results.sarif'

  trivy-image-scan:
    name: Container Image Scan
    runs-on: ubuntu-latest
    # 컨테이너 빌드 후 실행 (필요에 따라 needs: [build] 추가)
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .

      - name: Run Trivy image scan
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'image'
          image-ref: 'myapp:${{ github.sha }}'
          severity: 'CRITICAL'
          format: 'table'
          # CRITICAL 발견 시 파이프라인 종료
          exit-code: '1'

ignore-unfixed: true 옵션이 중요합니다. 아직 패치가 없는 취약점까지 모두 보고하면 수백 개의 알림이 나와 정작 중요한 것을 놓치게 됩니다. 수정 가능한 것만 필터링하면 실질적인 액션 아이템에 집중할 수 있습니다.

Dependabot으로 취약한 라이브러리 자동 패치 PR 받기

Dependabot은 레포지토리의 의존성 파일(package.json, requirements.txt, go.mod 등)을 분석해 취약한 버전의 패키지를 업데이트하는 PR을 자동으로 생성합니다.

.github/dependabot.yml을 작성합니다.

# Dependabot 자동 의존성 업데이트 설정
version: 2
updates:
  # Python 의존성 자동 업데이트
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
      time: "09:00"
      timezone: "Asia/Seoul"
    # 한 번에 생성할 PR 최대 수 (너무 많으면 관리 부담)
    open-pull-requests-limit: 5
    # 보안 업데이트만 자동 머지 허용
    labels:
      - "security"
      - "dependencies"

  # Docker 이미지 베이스 버전 자동 업데이트
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"

  # GitHub Actions 액션 버전 업데이트
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"

Dependabot이 생성한 보안 PR은 자동 머지를 설정해두는 것이 좋습니다. 레포지토리 Settings → Rules → Rulesets에서 Dependabot PR에 한해 CI 통과 시 자동 머지를 허용하면 라이브러리 취약점 패치 지연을 최소화할 수 있습니다.

소프트웨어 공급망 보안에 관심이 있다면 소프트웨어 공급망 공격 위험과 대응 전략도 함께 읽어보세요.

보안 스캔 결과를 팀이 실제로 활용하게 만드는 법

파이프라인을 구축해도 팀이 알림을 무시하면 무용지물입니다. 실무에서 가장 효과적인 방법은 심각도별 SLA(대응 시한) 정책을 먼저 합의하는 것입니다.

심각도 대응 시한 처리 방식
CRITICAL 24시간 이내 해당 PR 즉시 차단, 핫픽스 브랜치 생성
HIGH 7일 이내 다음 스프린트 이슈로 등록
MEDIUM 30일 이내 분기별 보안 스프린트에서 처리
LOW 분기별 검토 백로그 등록 후 우선순위 판단

또한 GitHub Security 탭에서 취약점을 'Dismissed'로 처리할 때 반드시 사유를 기재하도록 팀 규칙을 만드세요. "이 취약점은 우리 사용 패턴에서 실제로 악용이 불가능합니다"처럼 근거 있는 dismissal과 그냥 알림 닫기는 감사(audit) 관점에서 완전히 다릅니다.

자주 묻는 질문 (FAQ)

Q. CodeQL과 Trivy를 함께 써야 하나요, 하나만 써도 되나요?

둘은 탐지 대상이 다르므로 함께 쓰는 것이 권장됩니다. CodeQL은 소스코드의 로직 취약점(SQL 인젝션, XSS 등)을 분석하고, Trivy는 외부 의존성과 컨테이너 이미지의 알려진 CVE를 탐지합니다. 예산이나 시간 제약이 있다면 Trivy부터 도입하세요. 설정이 간단하고 즉각적인 가시성을 제공합니다.

Q. 공개 레포지토리가 아닌 프라이빗 레포지토리에서는 CodeQL이 유료인가요?

GitHub Advanced Security 라이선스가 필요합니다. 2024년 기준 GitHub Enterprise를 사용하는 조직에서 추가 비용이 발생합니다. 비용이 부담된다면 공개 레포지토리로 전환하거나, SonarQube Community Edition을 셀프호스팅하는 대안도 있습니다.

Q. Dependabot PR이 너무 많이 생성되어 관리가 힘든데 어떻게 줄이나요?

open-pull-requests-limit 값을 낮추고(예: 3), target-branchdevelop으로 설정해 main에 직접 PR이 쌓이는 것을 막으세요. 또는 allow 필드로 보안 업데이트(dependency-type: security) 만 받도록 제한하는 방법도 효과적입니다.

오늘 시작하는 DevSecOps 파이프라인 첫 단계

GitHub Actions CI/CD 파이프라인 보안 취약점 자동 탐지는 세 개의 YAML 파일로 시작합니다. 완벽한 정책을 먼저 세우려 하지 말고, 오늘 dependabot.yml 하나를 레포지토리에 추가해 첫 보안 PR이 생성되는 것부터 경험해보세요.

Trivy 파일시스템 스캔은 그 다음 단계입니다. 첫 스캔 결과를 보고 팀과 함께 "어떤 심각도까지 파이프라인을 차단할 것인가"를 논의하면, 자연스럽게 조직의 보안 정책이 구체화됩니다. CodeQL은 정적 분석 특성상 초기 설정 비용이 있지만, CRITICAL 알림 하나가 프로덕션 침해를 막는다면 투자 가치는 충분합니다.