본문 바로가기
CI.CD/Jenkins

[Jenkins] DevSecOps의 완성: Trivy로 컨테이너 보안 취약점 원천 차단하기

반응형

우리는 22단계에서 코드를 도커 이미지(Docker Image)로 굽는 화려한 기술을 배웠습니다. FROM node:14-alpine 같은 베이스 이미지를 가져와서 무심코 컨테이너를 말아 올렸죠. 그런데 만약 그 베이스 이미지 안에 해커가 악용할 수 있는 치명적인 OpenSSL 해킹 취약점(예: Heartbleed)이나 탈취 가능한 루트 권한 버그가 심어져 있다면 어떨까요? 젠킨스가 아무리 배포를 잘해도, 결과적으로는 '보안 폭탄'을 운영 서버 한가운데에 아주 빠르고 정확하게 배달해 주는 꼴이 됩니다. 바야흐로 배포 파이프라인 내부에 보안(Security)을 욱여넣어 강제화하는 DevSecOps(개발-보안-운영)의 시대입니다. 이번 [Jenkins 실무 완벽 가이드] 시리즈에서는 전 세계에서 가장 사랑받는 초경량 오픈소스 컨테이너 보안 스캐너인 'Trivy(트리비)'를 젠킨스와 연동하여, 보안 결함이 있는 이미지는 배포를 원천 차단해 버리는 극강의 방어 체계에 대하여 다뤄보겠습니다.

🛡️ 1. 왜 애플리케이션 코드가 아닌 '컨테이너'를 스캔하는가?

이전에 배운 SonarQube(21단계)는 개발자가 직접 타이핑한 Java나 Python 소스코드 자체의 논리적 버그를 잡아냅니다. 반면 Trivy는 코드가 아니라, 그 코드를 둘러싸고 있는 OS 패키지 환경(Alpine, Ubuntu 베이스 이미지)의 알려진 취약점(CVE)을 잡아냅니다.

개발자가 짠 console.log()는 완벽할지 몰라도, 그 밑바탕이 되는 리눅스의 curl 패키지나 openssl 버전에 심각한 구멍(Critical)이 뚫려있다면 시스템은 해킹당합니다. Trivy는 방금 갓 구워낸 도커 이미지의 배를 갈라서, 내부에 설치된 수백 개의 리눅스 라이브러리 목록을 뽑아낸 뒤 전 세계 보안 취약점 DB(CVE Database)와 실시간으로 대조하는 무서운 검열관 역할을 합니다.


⚠️ 2. Trivy의 무자비한 차단 로직 (Exit Code 1)

젠킨스 쉘(sh) 스크립트의 특징은, 명령어의 결과 값(Exit Code)이 0이면 성공(계속 진행)으로 간주하고, 0이 아닌 숫자(예: 1)가 떨어지면 즉시 파이프라인을 폭파(Failure) 시킨다는 점입니다.

Trivy에게 --exit-code 1 이라는 옵션과 --severity CRITICAL,HIGH (치명적, 높음 수준의 취약점만 찾아라) 옵션을 조합해서 던져봅시다. Trivy가 이미지를 훑어보다가 'LOW(낮음)' 수준의 자잘한 취약점만 발견하면 "에이, 이 정도는 봐준다"라며 0을 뱉고 통과시킵니다. 하지만 'HIGH(높음)' 이상의 무시무시한 취약점이 단 한 개라도 발견되는 순간, Trivy는 자비 없이 에러 코드 1을 내뿜어버립니다. 그 순간 젠킨스 파이프라인은 붉게 피를 흘리며(실패) 도커 푸시(Push) 단계로 넘어가지 못하고 완전히 차단됩니다. 완벽한 'Quality Gate(보안 게이트)'가 탄생하는 순간입니다.


💻 3. 실무 적용 극한의 DevSecOps 파이프라인 코드

젠킨스 워커 노드(호스트) 서버에 trivy 바이너리가 설치되어 있다고 가정하고(apt-get install trivy), 도커 빌드와 푸시 사이에 보안 검사 스테이지를 교묘하게 끼워 넣은 마스터피스 코드를 살펴봅시다.

pipeline {
    agent any
    environment {
        IMAGE_NAME = "my-dockerhub-id/my-secure-app:${env.BUILD_NUMBER}"
    }
    stages {
        stage('1. Docker Build') {
            steps {
                echo "도커 이미지를 굽습니다..."
                sh "docker build -t ${IMAGE_NAME} ."
            }
        }
        
        stage('2. Security Vulnerability Scan (Trivy)') {
            steps {
                echo "🔥 방금 구운 이미지에 대한 강력한 보안 스캔을 시작합니다!"
                
                // Trivy 스캔 명령어 분석:
                // --no-progress: 젠킨스 로그가 지저분해지지 않게 로딩바 숨김
                // --severity HIGH,CRITICAL: 중간(MEDIUM) 이하는 무시하고 고위험군만 색출
                // --exit-code 1: 하나라도 걸리면 파이프라인 즉시 폭파 (실무 핵심)
                sh """
                    trivy image \
                        --no-progress \
                        --severity HIGH,CRITICAL \
                        --exit-code 1 \
                        ${IMAGE_NAME}
                """
            }
        }
        
        stage('3. Docker Push (Only if Passed)') {
            steps {
                // 이 스테이지에 도달했다는 것은, 앞선 Trivy 보안 게이트를 무사히 통과한 청정 이미지라는 뜻입니다!
                echo "✅ 보안 스캔을 완벽하게 통과했습니다. 안전한 이미지를 레지스트리로 푸시합니다."
                withCredentials([usernamePassword(credentialsId: 'dockerhub-creds', passwordVariable: 'PASS', usernameVariable: 'USER')]) {
                    sh "echo \$PASS | docker login -u \$USER --password-stdin"
                    sh "docker push ${IMAGE_NAME}"
                }
            }
        }
    }
}

🎯 4. 마무리 및 다음 단계

지금까지 단순히 배포를 자동화하는 것을 넘어, 운영 환경에 독극물(취약점)이 살포되는 것을 기계적으로 막아내는 최강의 수문장, Trivy 스캐너와 젠킨스 Pipeline의 완벽한 DevSecOps 결합 기법에 대해 다루어 보았습니다. 이제 여러분의 회사는 보안 감사를 받을 때 "우리는 CI 과정에서 배포 전 모든 컨테이너의 CVE(취약점)를 실시간 차단하고 있습니다"라고 자랑스럽게 말할 수 있게 되었습니다.

코드 품질도 챙겼고(SonarQube), 도커 이미지 보안도 챙겼습니다(Trivy). 이제 여러분의 파이프라인은 완벽한 자동화 요새가 되었습니다. 그런데 Github에서 여러 개발자가 협업을 하다 보면 main 브랜치에 코드를 직접 밀어 넣는 것이 아니라, PR(Pull Request)을 올려서 코드 리뷰를 받고 머지(Merge)하는 것이 실무의 정석입니다. "누군가 PR을 올렸을 때, 그 코드를 머지하기 전에 젠킨스가 미리 슬쩍 가져와서 빌드가 잘 되는지 테스트해 주고 Github에 O, X 마크를 달아줄 순 없을까?" 이어지는 34단계 포스팅에서는 Github 협업 프로세스의 꽃, 'Github PR(Pull Request) 빌드 자동화와 커밋 스테이터스 연동법'에 대해 아주 치밀하게 파헤쳐 보겠습니다!

반응형