13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[CI/CD 보안] Trivy로 컨테이너 보안 취약점 자동 탐지 구축하기

[CI/CD 보안] Trivy로 컨테이너 보안 취약점 자동 탐지 구축하기

안녕하세요, 13년차 인프라 엔지니어입니다. 요즘 같은 DevOps 환경에서는 CI/CD 파이프라인이 선택이 아닌 필수가 되었죠. 그런데 이렇게 빠르게 빌드하고 배포하는 과정에서 보안(Security)은 제대로 챙기고 계신가요?

현업과 홈랩에서 일하다 보니 느끼는 게, 보안은 항상 뒷전으로 밀리거나 나중에 터지고 나서야 허둥지둥 해결하는 경우가 많더라고요. 특히 컨테이너 이미지는 한 번 빌드되면 어떤 취약점이 있는지 제대로 확인하지 않고 프로덕션 환경에 배포되는 일이 허다합니다. 그러다 터지면… 상상하기도 싫죠.

이런 문제를 미리 막고 싶어서, CI/CD 파이프라인에 보안 취약점 자동 탐지(Automated Vulnerability Scanning)를 도입하는 방법을 계속 고민해왔습니다. 그리고 찾은 게 바로 Trivy(트리비)입니다. 오늘은 Trivy를 활용해서 CI/CD 파이프라인에 컨테이너 이미지 보안 스캔을 자동화하는 방법을 제 경험을 바탕으로 솔직하게 풀어보려 합니다.

참고: 본 글은 보안 학습과 자신이 관리하는 시스템 방어를 위한 교육 목적입니다. 타인의 시스템에 무단 접근하는 행위는 법률상 불법이며 처벌 대상이니 꼭 기억해두세요.

CI/CD 파이프라인에 Trivy가 통합되어 컨테이너 이미지의 보안 취약점을 자동 탐지하는 아키텍처 다이어그램

그림 1: Trivy를 활용한 CI/CD 보안 파이프라인 개요

Trivy란 무엇인가? 컨테이너 보안 스캔 도구 개론

Trivy는 Aqua Security에서 개발한 오픈소스 도구로, 컨테이너 이미지(Container Image), 파일 시스템(Filesystem), Git 저장소(Git Repository) 등 다양한 대상에서 보안 취약점(Security Vulnerabilities)과 잘못된 설정(Misconfigurations)을 찾아줍니다. 가볍고 빠르면서도 정확도가 높아서 많은 개발팀이 애용하고 있거든요.

쉽게 말해, 우리가 만든 컨테이너 이미지 안에 혹시 오래된 라이브러리나 알려진 취약점이 있는 패키지가 포함되어 있지는 않은지, 혹은 Dockerfile이나 Kubernetes 설정 파일에 보안상 위험한 설정이 있지는 않은지 꼼꼼하게 검사해주는 보안 스캐너라고 생각하시면 됩니다. 저도 처음엔 반신반의했는데, 써보고 나서는 정말 감탄했어요.

왜 CI/CD 파이프라인에 보안 스캔을 넣어야 할까요?

DevOps 환경에서는 개발 단계에서부터 보안을 고려하는 Shift-Left Security(시프트 레프트 보안)가 중요합니다. 나중에 터지고 나서 고치려면 시간과 비용이 훨씬 많이 들거든요. 저도 예전에 프로덕션에 배포된 서비스에서 심각한 취약점이 발견돼서 밤샘 작업을 한 기억이 생생합니다.

CI/CD 파이프라인에 Trivy 같은 도구를 넣으면:

  • 조기 발견 및 대응: 개발 초기에 취약점을 발견해서 빠르게 수정할 수 있습니다.
  • 자동화된 검증: 매번 수동으로 검사할 필요 없이, 코드가 푸시될 때마다 자동으로 보안 검증이 이루어집니다.
  • 보안 수준 향상: 잠재적인 보안 위협을 줄여 전체 시스템의 보안 견고성(Security Robustness)을 높일 수 있습니다.
  • 규제 준수: PCI-DSS, HIPAA 같은 특정 산업군의 보안 규제를 준수하는 데 도움이 됩니다.

Trivy 실전 구축! CI/CD 파이프라인에 녹여내기

이제 가장 중요한 실전 구현입니다. 저는 주로 GitLab CI/CD를 사용하는데요, 여기서는 GitLab CI를 예시로 보여드리겠습니다. 다른 CI/CD 도구(GitHub Actions, Jenkins 등)에서도 원리는 비슷하니 응용하시면 됩니다.

1. Trivy 설치 및 기본 스캔 (로컬 환경)

먼저 로컬에서 Trivy가 잘 작동하는지 확인해봐야겠죠? 설치는 정말 간단합니다. 저는 주로 Homebrew를 쓰지만, 다양한 설치 방법이 있어요.

# macOS (Homebrew) 또는 Linux (apt, yum 등 각 배포판 패키지 매니저 활용)
brew install aquasecurity/trivy/trivy

# 또는 Docker로 실행 (설치 없이 바로 사용 가능)
docker run --rm aquasecurity/trivy:latest --version

설치가 완료되면, 이제 컨테이너 이미지를 스캔해봅시다. 저는 테스트용으로 NGINX 공식 이미지를 스캔해볼게요.

trivy image nginx:latest

명령어를 실행하면 NGINX 이미지에 포함된 패키지들의 취약점 목록이 쭉 나올 겁니다. 심각도(Severity)별로 분류되어 있어서 어떤 것부터 고쳐야 할지 한눈에 파악하기 좋더라고요. 처음엔 이 많은 취약점들을 어떻게 다 봐야 하나 당황했는데, 실제로는 Critical이나 High 레벨부터 우선순위를 두고 보면 됩니다.

2. GitLab CI/CD 파이프라인에 Trivy 통합

이제 로컬에서 잘 작동하는 Trivy를 CI/CD 파이프라인에 넣어봅시다. 제 경험상, 컨테이너 이미지를 빌드한 직후, 그리고 레지스트리(Registry)로 푸시하기 전에 스캔하는 것이 가장 효율적이었습니다. 이렇게 하면 취약한 이미지가 레지스트리에 올라가는 것을 사전에 차단할 수 있거든요.

`.gitlab-ci.yml` 파일에 다음과 같은 내용을 추가할 수 있습니다.

stages:
  - build
  - scan
  - deploy

variables:
  DOCKER_IMAGE_NAME: my-app
  DOCKER_IMAGE_TAG: $CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA
  # CI_REGISTRY는 GitLab 내장 Registry 주소입니다.
  FULL_IMAGE_NAME: $CI_REGISTRY/$CI_PROJECT_PATH/$DOCKER_IMAGE_NAME:$DOCKER_IMAGE_TAG

build_image:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $FULL_IMAGE_NAME .
    - docker push $FULL_IMAGE_NAME
  only:
    - main
    - merge_requests

scan_image_with_trivy:
  stage: scan
  image: 
    name: aquasecurity/trivy:latest
    entrypoint: [""]
  variables:
    # Trivy가 취약점 DB를 다운로드 받을 디렉토리. CI/CD 캐시 활용을 위해 설정
    TRIVY_CACHE_DIR: ".trivycache"
  script:
    # 빌드된 이미지를 스캔하기 위해 Docker Registry에 로그인
    - trivy --version
    - trivy image --ignore-unfixed --severity CRITICAL,HIGH --exit-code 1 $FULL_IMAGE_NAME
  cache:
    key: "$CI_COMMIT_REF_SLUG-trivy-cache"
    paths:
      - "$TRIVY_CACHE_DIR"
    policy: pull-push
  only:
    - main
    - merge_requests

deploy:
  stage: deploy
  script:
    - echo "Deploying $FULL_IMAGE_NAME"
    # 여기에 실제 배포 로직 (Kubernetes, Ansible 등)을 작성합니다.
  only:
    - main

위 YAML 코드를 보시면, `scan_image_with_trivy`라는 새로운 stage를 추가했습니다. 여기서 주목할 부분은:

  • image: aquasecurity/trivy:latest: Trivy 공식 Docker 이미지를 사용해서 별도 설치 없이 바로 실행합니다.
  • --ignore-unfixed: 아직 패치가 나오지 않은 취약점은 결과에서 제외합니다. (이걸 안 하면 리포트가 너무 길어져서 피로도가 높더라고요.)
  • --severity CRITICAL,HIGH: Critical(치명적)과 High(높음) 심각도의 취약점만 보고합니다. 처음부터 모든 취약점을 잡으려다가는 배보다 배꼽이 더 커질 수 있습니다. 현실적으로 가장 위험한 것부터 처리하는 게 중요하더라고요.
  • --exit-code 1: Critical 또는 High 심각도의 취약점이 발견되면, 파이프라인을 실패(Exit Code 1)시킵니다. 이게 핵심입니다! 자동으로 취약한 이미지가 다음 단계로 넘어가지 못하게 막는 거죠.
  • cache: Trivy가 취약점 데이터베이스(DB)를 다운로드하는 시간을 줄이기 위해 캐시를 활용했습니다. CI/CD 환경에서는 캐시 활용이 빌드 시간을 줄이는 데 아주 중요하거든요.
GitLab CI/CD에서 Trivy 스캔 작업이 실행되고 CRITICAL, HIGH 취약점이 표시된 결과 화면

그림 2: GitLab CI/CD에서 Trivy 스캔 작업 실행 및 결과

Trivy 도입 시 주의사항 및 트러블슈팅

Trivy CI/CD 파이프라인을 구축하면서 겪었던 몇 가지 삽질 경험과 해결책을 공유합니다. 혹시 비슷한 문제를 겪으신다면 도움이 될 거예요!

1. False Positive (오탐) 문제

Trivy도 완벽하지 않습니다. 때로는 실제로는 문제가 없는데 취약점으로 보고하는 오탐(False Positive)이 발생하기도 합니다. 특히 개발 초기 단계에서는 이런 오탐 때문에 파이프라인이 계속 실패하면 개발자들의 불만이 커질 수 있거든요.

  • 해결책: .trivyignore 파일을 사용해서 특정 취약점 ID를 무시하거나, --ignore-unfixed 옵션을 활용하여 아직 패치되지 않은 취약점을 제외할 수 있습니다. 예를 들어, CVE-2023-12345라는 특정 취약점 ID를 무시하고 싶다면 .trivyignore 파일에 해당 ID를 한 줄에 하나씩 작성하면 됩니다.

2. 너무 많은 취약점 보고서

처음에는 모든 심각도를 스캔했더니 보고서가 너무 길고, 뭘 먼저 고쳐야 할지 막막하더라고요. 모든 취약점을 한 번에 다 고치려는 건 현실적으로 어렵습니다.

  • 해결책: 앞서 보여드린 것처럼 --severity CRITICAL,HIGH 옵션을 사용해서 가장 위험한 취약점부터 우선적으로 처리하도록 정책을 세우는 것이 좋습니다. 점진적으로 심각도 기준을 높여가는 거죠.

3. Trivy DB 업데이트 실패

CI/CD 환경에서 네트워크 문제나 프록시 설정 때문에 Trivy가 취약점 데이터베이스를 업데이트하지 못하는 경우가 있었습니다. 최신 DB가 아니면 정확한 스캔이 불가능하죠.

  • 해결책: CI/CD Runner가 외부 인터넷에 접근 가능한지 확인하고, 필요한 경우 프록시 환경 변수(HTTP_PROXY, HTTPS_PROXY)를 설정해줘야 합니다. GitLab CI의 경우, variables 섹션에서 설정할 수 있습니다.
  • 또한, trivy image 명령 전에 trivy sbom으로 SBOM(Software Bill Of Materials)을 생성하고, 이를 기반으로 trivy image --input sbom.json 형태로 스캔하는 방식도 고려해볼 수 있습니다. 이는 네트워크 제한 환경에서 유용합니다.

CI/CD 파이프라인 보안 검증: 실제 스캔 결과 및 적용

위 설정대로 파이프라인을 구축하고 나면, 이제 새로운 코드가 푸시되거나 Merge Request(머지 리퀘스트)가 생성될 때마다 자동으로 Trivy 스캔이 실행됩니다. 만약 Critical, High 심각도의 취약점이 발견되면, 스캔 단계에서 파이프라인이 실패하고, 개발자에게 알림이 갑니다. 개발자는 이 알림을 보고 취약점을 수정하거나, 정당한 오탐(False Positive)인 경우 무시 규칙을 추가할 수 있습니다.

실제 GitLab CI/CD 파이프라인에서 성공적으로 스캔이 완료된 모습이나, 혹은 취약점 때문에 파이프라인이 실패한 모습을 보면 정말 뿌듯하더라고요. 저는 주로 이런 식으로 결과를 확인합니다.

Trivy 스캔 결과가 심각도별로 요약되고 해결 방안을 제시하는 대시보드 리포트

그림 3: Trivy 스캔 결과 대시보드 예시

아래는 Trivy의 주요 스캔 대상과 그 특징을 비교한 표입니다. 상황에 따라 적절한 스캔 대상을 선택하는 것이 중요하죠.

스캔 대상 (Scan Target) 설명 (Description) 주요 활용 사례 (Key Use Cases) 장점 (Pros) 고려사항 (Considerations)
image (컨테이너 이미지) Docker 이미지, OCI 이미지 등 컨테이너 이미지 내부의 패키지 취약점 스캔 CI/CD 파이프라인에서 이미지 빌드 후 즉시 검사, 배포 전 최종 검증 가장 일반적이고 강력한 컨테이너 보안, 배포 전 위험 제거 스캔 시간이 다소 길 수 있음 (레이어 분석), 이미지 레이어 최적화 필요
fs (파일 시스템) 로컬 파일 시스템 또는 압축 파일 내의 취약점 및 설정 오류 스캔 개발 중인 프로젝트 코드 스캔, 특정 디렉토리/파일 검사 빠른 스캔, 개발 초기 단계에서 피드백 제공, CI/CD 전 로컬 검증 전체 컨테이너 환경을 반영하기 어려움, 의존성 설치 환경에 따라 결과 상이
repo (Git 저장소) Git 저장소의 설정 파일(Dockerfile, Kubernetes YAML)에서 잘못된 설정 스캔 IaC(Infrastructure as Code) 보안 검증, 초기 개발 단계에서 설정 오류 방지 코드 레벨에서 보안 취약점 조기 발견, 설정 파일 검증 코드 내부의 라이브러리 취약점은 탐지 불가, 설정 파일 문법 의존적
Trivy의 컨테이너 이미지, 파일 시스템, Git 저장소 스캔 대상별 특징 비교 인포그래픽

그림 4: Trivy 스캔 대상별 특징 비교

마무리하며: Trivy를 통한 지속적인 보안 확보

Trivy를 CI/CD 파이프라인에 통합하는 것은 컨테이너 기반 환경에서 보안을 강화하고, 개발 및 운영 효율성을 높이는 중요한 단계라고 생각합니다. 저도 처음엔 ‘이걸 언제 다 구축하나’ 싶었는데, 한 번 구축해두니 정말 든든하더라고요. 든든한 방패를 얻은 기분이랄까요?

물론 Trivy 하나만으로 모든 보안 위협을 막을 수는 없습니다. 하지만 가장 기본적인 방어선을 구축하고, 자동화된 방식으로 지속적인 보안 검증을 수행한다는 점에서 그 가치는 충분하다고 봅니다. Critical, High 레벨의 취약점만이라도 배포 전에 걸러낼 수 있다면, 야간 비상 호출(On-call) 횟수를 확 줄일 수 있을 거예요. 저도 덕분에 잠을 좀 더 잘 수 있게 됐습니다.

만약 여러분의 CI/CD 파이프라인에 아직 자동화된 보안 스캔이 없다면, 지금 당장 Trivy를 도입해보시길 강력히 추천합니다. 초기에는 오탐이나 너무 많은 보고서 때문에 조금 번거로울 수도 있지만, 꾸준히 규칙을 개선하고 피드백을 반영하다 보면 훨씬 견고하고 효율적인 보안 프로세스를 만들 수 있을 겁니다. 다음번에는 Trivy를 활용한 SBOM(Software Bill Of Materials) 생성 및 관리 방법에 대해 이야기해볼까 합니다. 기대해주세요!