Gymterview
senior

Как встроить проверки безопасности в CI/CD пайплайн?

Security gates — обязательные проверки безопасности в пайплайне, которые блокируют продвижение артефакта при обнаружении проблем. Принцип shift-left security подразумевает выявление уязвимостей как можно раньше — на этапе написания кода и сборки, а не после деплоя в production.

Полный безопасный пайплайн (Jenkinsfile):

Пример
pipeline {
    agent any

    environment {
        REGISTRY = 'registry.bank.local'
        IMAGE_NAME = 'banking-service'
        IMAGE_TAG = "${BUILD_NUMBER}-${GIT_COMMIT.take(8)}"
    }

    stages {
        // ===== Stage 1: Статический анализ кода (SAST) =====
        stage('SAST - Static Analysis') {
            parallel {
                stage('SonarQube') {
                    steps {
                        sh '''
                            ./mvnw sonar:sonar \
                                -Dsonar.host.url=${SONAR_URL} \
                                -Dsonar.token=${SONAR_TOKEN} \
                                -Dsonar.qualitygate.wait=true
                        '''
                    }
                }
                stage('SpotBugs + FindSecBugs') {
                    steps {
                        sh './mvnw spotbugs:check'
                    }
                }
                stage('Secret Detection') {
                    steps {
                        // Поиск случайно добавленных секретов в исходном коде
                        sh 'trivy fs --scanners secret --exit-code 1 .'
                    }
                }
            }
        }

        // ===== Stage 2: Проверка зависимостей (SCA) =====
        stage('SCA - Dependency Check') {
            steps {
                sh '''
                    ./mvnw dependency-check:check \
                        -DfailBuildOnCVSS=7 \
                        -Dformat=JSON \
                        -DprettyPrint=true
                '''
            }
            post {
                always {
                    archiveArtifacts 'target/dependency-check-report.json'
                }
            }
        }

        // ===== Stage 3: Сборка и тесты =====
        stage('Build & Test') {
            steps {
                sh './mvnw clean verify'
            }
        }

        // ===== Stage 4: Сборка Docker-образа =====
        stage('Docker Build') {
            steps {
                sh """
                    docker build \
                        --no-cache \
                        --label "org.opencontainers.image.revision=${GIT_COMMIT}" \
                        --label "org.opencontainers.image.created=\$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
                        -t ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} .
                """
            }
        }

        // ===== Stage 5: Сканирование образа =====
        stage('Image Security Scan') {
            parallel {
                stage('Trivy Scan') {
                    steps {
                        sh """
                            trivy image \
                                --exit-code 1 \
                                --severity CRITICAL,HIGH \
                                --ignore-unfixed \
                                --format json \
                                --output trivy-report.json \
                                ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
                        """
                    }
                }
                stage('Dockerfile Lint') {
                    steps {
                        sh 'hadolint Dockerfile --failure-threshold warning'
                    }
                }
                stage('SBOM Generation') {
                    steps {
                        sh """
                            trivy image \
                                --format cyclonedx \
                                --output sbom.cdx.json \
                                ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
                        """
                    }
                }
            }
        }

        // ===== Stage 6: Проверка Kubernetes-манифестов =====
        stage('IaC Security') {
            steps {
                sh '''
                    # Проверка Kubernetes манифестов на мисконфигурации
                    trivy config --exit-code 1 --severity CRITICAL,HIGH k8s/

                    # Дополнительная проверка с помощью kubesec
                    kubesec scan k8s/deployment.yaml
                '''
            }
        }

        // ===== Stage 7: Push и подпись образа =====
        stage('Push & Sign') {
            steps {
                sh """
                    docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}

                    # Подпись образа с помощью Cosign
                    cosign sign --key \${COSIGN_KEY} \
                        ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}

                    # Прикрепление SBOM к образу в реестре
                    cosign attach sbom \
                        --sbom sbom.cdx.json \
                        ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
                """
            }
        }

        // ===== Stage 8: Динамическое тестирование (DAST) =====
        stage('DAST - Dynamic Testing') {
            when {
                branch 'develop'
            }
            steps {
                sh '''
                    # Деплой в тестовую среду
                    kubectl apply -f k8s/ -n testing

                    # Запуск OWASP ZAP для динамического сканирования
                    docker run --rm owasp/zap2docker-stable zap-baseline.py \
                        -t http://banking-service.testing.svc:8080 \
                        -r zap-report.html
                '''
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: '*.json, *.html', allowEmptyArchive: true
        }
        failure {
            // Уведомление команды безопасности о провале security gate
            slackSend channel: '#security-alerts',
                color: 'danger',
                message: "Security gate FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
        }
    }
}

Пайплайн выстроен так, что каждый последующий этап выполняется только при успехе предыдущего. Если на любом этапе обнаружена критическая проблема (--exit-code 1), пайплайн останавливается и образ не попадает в реестр. Параллельные этапы (parallel) ускоряют выполнение, сохраняя полноту проверок.

Сводная таблица типов проверок безопасности:

Тип Инструменты Что проверяет Когда запускать
SAST (Static Application Security Testing) SonarQube, SpotBugs + FindSecBugs, Semgrep Уязвимости в исходном коде: SQL injection, XSS, небезопасная десериализация При каждом коммите
SCA (Software Composition Analysis) OWASP Dependency Check, Snyk Уязвимости в сторонних зависимостях (библиотеках) При сборке
Secret Scan Trivy, GitLeaks, TruffleHog Случайно добавленные секреты: пароли, ключи API, токены в коде и Git-истории При каждом коммите
Image Scan Trivy, Snyk, Grype Уязвимости в Docker-образе: пакеты ОС и зависимости приложения После сборки образа
IaC Scan Trivy config, Checkov, kubesec Мисконфигурации: Kubernetes-манифесты, Dockerfile, Terraform При каждом коммите
DAST (Dynamic Application Security Testing) OWASP ZAP Уязвимости работающего приложения: проверка HTTP-эндпоинтов, заголовков, аутентификации После деплоя в тестовую среду
Lint hadolint, yamllint Стилистические и безопасностные проблемы в Dockerfile и YAML При каждом коммите

Ключевой принцип: пайплайн должен блокировать продвижение артефакта при обнаружении критических уязвимостей. Для банковских систем ни одна CRITICAL-уязвимость не должна попасть в production. Параметр --exit-code 1 в сканерах обеспечивает это: при обнаружении уязвимости указанного severity процесс завершается с ненулевым кодом, и CI/CD-система считает этап провалившимся.