0

Hướng dẫn Setup Jenkins cho hệ thống Monolith - Bài 16: Tích hợp kiểm thử bảo mật mã nguồn mở (SAST/Dependency Check) vào Pipeline

Ở Bài 13 đến Bài 15, chúng ta đã dùng SonarQube và Code Coverage để kiểm tra rất kỹ phần code do chính đội ngũ lập trình viên viết ra. Nhưng có một sự thật đáng giật mình trong các hệ thống Monolith hiện đại: Code bạn tự viết chỉ chiếm khoảng 15% - 20% dung lượng dự án; 80% còn lại nằm trong thư mục vendor/, node_modules/, go.mod hoặc pom.xml (các thư viện mã nguồn mở bên thứ ba).

Mà bạn nhớ lại ở Bài 13 xem? Chúng ta đã cấu hình sonar.exclusions=**/vendor/**,**/node_modules/** để SonarQube bỏ qua toàn bộ thư mục thư viện! Vậy nếu một gói thư viện xử lý PDF, JWT hay Log mà bạn cài từ 2 năm trước bỗng dưng bị phát hiện lỗ hổng Zero-day nghiêm trọng (như thảm họa Log4Shell trên Java hay các lỗ hổng Remote Code Execution trong các gói Composer/NPM), làm sao Jenkins phát hiện và chặn lại trước khi đẩy lên Production?

Đó chính là lúc chúng ta đưa tư duy DevSecOps vào Pipeline với bộ đôi: SAST (Static Application Security Testing) và SCA (Software Composition Analysis / Dependency Check).

1. Phân biệt SAST và Dependency Check (SCA) trong hệ thống Monolith

Để thiết kế một hàng rào bảo mật không bị chồng chéo hay bỏ sót, bạn cần nắm rõ vai trò của hai chốt chặn này:

Tiêu chí so sánh SAST (Static Application Security Testing) SCA / Dependency Check (Software Composition Analysis)
Đối tượng quét chính Mã nguồn nội bộ do lập trình viên trong công ty tự viết (app/, src/, cmd/,...). Các thư viện của bên thứ ba và các gói phụ thuộc bắc cầu (Transitive Dependencies) được khai báo trong composer.lock, package-lock.json, go.sum, pom.xml,...
Mục tiêu phát hiện Lỗi logic bảo mật và cấu hình sai: SQL Injection, XSS, Path Traversal, hoặc lộ mật khẩu/API Key trực tiếp trong mã nguồn (Secret Scanning). Đối chiếu phiên bản thư viện đang sử dụng với cơ sở dữ liệu lỗ hổng bảo mật quốc tế (CVE / NVD) để tìm các gói đã bị công khai lỗ hổng.
Công cụ tiêu biểu SonarQube, Semgrep, Gitleaks,... Trivy (Khuyên dùng), OWASP Dependency-Check, hoặc các công cụ audit gốc theo ngôn ngữ (composer audit, npm audit, govulncheck).

2. Tại sao hệ thống Monolith là "miếng mồi ngon" của lỗ hổng Dependency?

Khác với Microservices (nơi mỗi service chỉ cài một vài thư viện nhỏ phục vụ đúng 1 chức năng), hệ thống Monolith tích hợp đủ mọi thứ vào chung một ngôi nhà: từ xuất file Excel, xử lý ảnh, gửi mail, thanh toán, kết nối Kafka, Redis, đến tạo PDF.

  • Hiệu ứng phụ thuộc bắc cầu (Transitive Dependencies): Bạn chỉ khai báo cài 30 gói chính trong composer.json hoặc package.json, nhưng 30 gói đó lại kéo theo 400 gói con khác.

  • Nỗi sợ nâng cấp ("Đang chạy ổn đừng đụng vào"): Các dự án Monolith lâu năm rất ngại chạy composer update hay npm update vì sợ gãy hệ thống, dẫn đến việc tồn đọng hàng chục thư viện chứa lỗ hổng bảo mật mức độ CRITICAL hoặc HIGH suốt nhiều năm mà không ai hay biết.

3. Lựa chọn vũ khí quét Dependency cho Jenkins Pipeline

Trong thực tế triển khai trên Jenkins cho hệ thống Monolith, có 3 phương án phổ biến nhất từ nhẹ đến toàn diện. Bạn có thể chọn 1 hoặc kết hợp cả 2:

Phương án 1: Sử dụng công cụ Audit chính chủ của từng ngôn ngữ (Nhanh nhất, 0 cần cài đặt thêm)

Các hệ sinh thái hiện đại đều đã tích hợp sẵn lệnh kiểm tra CVE trực tiếp vào trình quản lý gói:

  • PHP / Laravel (Từ Composer 2.4+):

    Bash

    # Quét file composer.lock và xuất báo cáo định dạng JSON hoặc dừng ngay nếu có lỗ hổng
    composer audit --format=plain
    
    
  • Golang (Sử dụng govulncheck chính chủ từ Go Team): (Điểm ăn tiền của govulncheck là nó không chỉ quét file go.mod, mà còn phân tích cây gọi hàm xem code Monolith của bạn có thực sự gọi tới cái hàm bị lỗi trong thư viện đó hay không, giúp giảm báo động giả tối đa).

    Bash

    go install golang.org/x/vuln/cmd/govulncheck@latest
    govulncheck ./...
    
    
  • Node.js / NestJS / Express:

    Bash

    # Chỉ báo lỗi và chặn Pipeline nếu phát hiện lỗ hổng từ mức High hoặc Critical trở lên
    npm audit --audit-level=high --omit=dev
    
    

Phương án 2: Sử dụng Trivy (Công cụ All-in-One hiện đại và nhanh nhất hiện nay)

Nếu bạn muốn một công cụ duy nhất quét được mọi ngôn ngữ (PHP, Go, Java, Node.js, Python), quét được cả lỗ hổng thư viện (SCA), quét lộ mật khẩu (Secret Scanning) và quét luôn cả cấu hình Docker sau này với tốc độ chỉ vài giây, Trivy (của Aqua Security) là lựa chọn số 1:

Bash

# Cài đặt nhanh Trivy trên Linux Jenkins Server
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin

# Lệnh quét toàn bộ thư mục dự án Monolith:
# --exit-code 1: Tự động đánh FAIL Pipeline nếu phát hiện lỗ hổng mức CRITICAL
trivy fs --scanners vuln,secret --severity HIGH,CRITICAL --exit-code 1 .

Phương án 3: OWASP Dependency-Check Plugin (Chuẩn kiểm toán doanh nghiệp / Enterprise)

Nếu dự án Monolith của bạn làm cho khối Ngân hàng, Tài chính hoặc khách hàng Nhật Bản/Châu Âu yêu cầu phải có báo cáo giao diện đồ họa chuẩn OWASP trên Jenkins UI:

  1. Vào Manage Jenkins -> Plugins -> Cài đặt plugin OWASP Dependency-Check.

  2. Vào Manage Jenkins -> Tools -> Dependency-Check installations -> Bấm Add Dependency-Check, đặt tên là OWASP-DC và chọn Install automatically. (Lưu ý: Lần chạy đầu tiên của OWASP Dependency-Check sẽ mất khoảng 10-15 phút để tải toàn bộ cơ sở dữ liệu lỗ hổng NVD về máy chủ Jenkins).

4. Tích hợp Stage Security & Dependency Check hoàn chỉnh vào Jenkinsfile

Dưới đây là cấu trúc Jenkinsfile chuẩn DevSecOps cho hệ thống Monolith: Kết hợp quét lỗ hổng thư viện, quét lộ Secret Key và xuất báo cáo trực quan trước khi cho phép đóng gói Artifact:

Groovy

pipeline {
    agent any

    options {
        buildDiscarder(logRotator(numToKeepStr: '15'))
        timeout(time: 30, unit: 'MINUTES')
        timestamps()
    }

    environment {
        APP_NAME = "monolith-core-system"
        BUILD_VERSION = "v1.0.${env.BUILD_NUMBER}"
    }

    stages {
        stage('1. Checkout Source Code') {
            steps {
                checkout scm
            }
        }

        stage('2. Install Dependencies') {
            steps {
                sh 'composer install --no-interaction --prefer-dist'
            }
        }

        stage('3. Run Unit Tests & Coverage') {
            environment {
                APP_ENV = "testing"
                DB_CONNECTION = "sqlite"
                DB_DATABASE = ":memory:"
                XDEBUG_MODE = "coverage"
            }
            steps {
                sh '''
                    mkdir -p reports
                    ./vendor/bin/phpunit \
                        --log-junit reports/junit.xml \
                        --coverage-clover reports/coverage-clover.xml
                '''
            }
            post {
                always {
                    junit testResults: 'reports/junit.xml', allowEmptyResults: false
                }
            }
        }

        // STAGE MỚI: Kiểm thử bảo mật thư viện mở (SCA) và quét lộ Secret (SAST)
        stage('4. Security & Dependency Check') {
            steps {
                echo "=== Bắt đầu quét lỗ hổng bảo mật trong các gói thư viện (Dependencies) ==="
                script {
                    sh '''
                        mkdir -p reports/security

                        # --- CÁCH 1: Sử dụng công cụ gốc của ngôn ngữ (Ví dụ: Composer Audit / GoVulnCheck) ---
                        echo "1. Chạy kiểm tra lỗ hổng thư viện bằng Native Audit..."
                        composer audit --no-dev --format=plain || true

                        # --- CÁCH 2: Sử dụng Trivy quét sâu CVE và lộ Secret Key trong mã nguồn ---
                        echo "2. Quét toàn diện CVE và Hardcoded Secrets bằng Trivy..."
                        trivy fs \
                            --scanners vuln,secret \
                            --skip-dirs tests,storage \
                            --severity HIGH,CRITICAL \
                            --format table \
                            --output reports/security/trivy-report.txt \
                            --exit-code 0 .
                        
                        # In kết quả ra màn hình Console của Jenkins để Dev dễ theo dõi
                        cat reports/security/trivy-report.txt

                        # Chốt chặn cứng: Nếu phát hiện lỗ hổng mức CRITICAL (Mức nguy hiểm cao nhất), lập tức chặn Pipeline!
                        trivy fs --scanners vuln,secret --severity CRITICAL --exit-code 1 .
                    '''

                    /* --- CÁCH 3 (Tùy chọn): Nếu bạn dùng OWASP Dependency-Check Plugin ---
                    dependencyCheck additionalArguments: ''' 
                        --scan ./ 
                        --exclude "**/tests/**" 
                        --format XML 
                        --format HTML 
                        --out reports/security 
                    ''', odcInstallation: 'OWASP-DC'
                    
                    dependencyCheckPublisher pattern: 'reports/security/dependency-check-report.xml',
                        failedTotalCritical: 1 // Đánh FAIL ngay nếu có >= 1 lỗi Critical
                    */
                }
            }
            post {
                always {
                    // Lưu trữ file báo cáo bảo mật để đội ngũ có thể tải về xem chi tiết
                    archiveArtifacts artifacts: 'reports/security/**', allowEmptyArchive: true
                }
            }
        }

        stage('5. SonarQube Static Analysis') {
            steps {
                script {
                    def scannerHome = tool 'SonarScanner-CLI'
                    withSonarQubeEnv('SonarQube-Main-Server') {
                        sh "${scannerHome}/bin/sonar-scanner -Dsonar.projectVersion=${BUILD_VERSION}"
                    }
                }
            }
        }

        stage('6. Quality Gate Check') {
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }

        stage('7. Clean & Package Artifact') {
            steps {
                sh '''
                    composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
                    tar --exclude='.git' --exclude='tests' --exclude='reports' -czf ${APP_NAME}-${BUILD_VERSION}.tar.gz .
                '''
            }
        }
    }

    post {
        success {
            archiveArtifacts artifacts: "${APP_NAME}-${BUILD_VERSION}.tar.gz", fingerprint: true
        }
        failure {
            echo "=== CẢNH BÁO BẢO MẬT / CHẤT LƯỢNG: Pipeline đã bị chặn do phát hiện lỗ hổng nghiêm trọng hoặc lỗi kiểm thử! ==="
        }
        always {
            sh 'rm -rf reports/ .env'
        }
    }
}

5. Kinh nghiệm thực chiến: Xử lý "Báo động giả" (False Positives) trong Monolith

Khi tích hợp công cụ quét Dependency vào một hệ thống Monolith đã chạy nhiều năm, bạn rất dễ gặp tình huống: Một thư viện bị báo lỗi CRITICAL (ví dụ: lỗ hổng trong hàm xuất XML của một gói thư viện lớn), nhưng thực tế dự án của bạn chỉ dùng hàm xuất CSV của gói đó chứ không hề đụng tới hàm XML, đồng thời tác giả thư viện vẫn chưa tung ra bản vá mới.

Nếu để --exit-code 1 cứng nhắc, cả công ty sẽ không thể deploy code mới. Cách xử lý chuẩn chuyên nghiệp như sau:

  1. Tuyệt đối không tắt tính năng quét bảo mật trên Pipeline.

  2. Sử dụng tệp danh sách loại trừ có kiểm soát (.trivyignore hoặc suppression.xml của OWASP):

    • Tạo tệp .trivyignore ngay tại thư mục gốc của dự án Monolith.

    • Ghi rõ mã CVE tạm thời bỏ qua kèm ngày hết hạn (Expired Date) để nhắc nhở đội ngũ cập nhật sau:

      Plaintext

      # Tạm thời bỏ qua CVE-2024-12345 vì hàm này không sử dụng trong hệ thống Monolith.
      # Sẽ cập nhật khi thư viện lên bản v3.2 (Hết hạn bỏ qua vào ngày 01/12/2026)
      CVE-2024-12345 exp:2026-12-01
      
      
  3. Chỉ quét các thư viện thực sự đi lên Production (--no-dev / --omit=dev):

    • Các lỗ hổng nằm trong bộ công cụ chỉ dùng lúc code/test (như phpunit, faker, eslint, webpack-dev-server) không tồn tại trong gói Artifact trên Production, nên hãy loại trừ các gói dev-dependencies khi xét tiêu chí chặn Pipeline để tránh làm phiền lập trình viên vô ích.

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí