Integrated CI security scanning

6 minute read

Use CloudBees platform’s security scanning features to maintain the security and integrity of your software assets. Configure your Jenkinsfile to run a security scan with a variety of popular tool types, including software composition analysis (SCA), static application security testing (SAST), and secret scanning. You can also set up automatic, implicit analysis of your integrated CI pipeline source code, ensuring that your software is continually assessed for vulnerabilities and risks.

Prerequisites

Set up CloudBees platform and your CI controller to work together. For more information, including technical requirements and limitations, refer to Getting started.

Set up security scanning on your Multibranch Pipeline

Set up your Jenkinsfile to install scanning tools, run scans, and publish security scan reports for ingestion to CloudBees platform, to leverage its enhanced analytics.

To enable security scan reports from your CI build to CloudBees platform:

Table 1. Available scanning tools in CloudBees platform.
Scanning tool Type

Black Duck

SCA

Checkov

SAST

CodeQL

SAST

findsecbugs

SAST

Gitleaks

Secrets

Gosec

SAST

Grype

SAST

njsscanner

SAST

Snyk

SAST

Trivy

Container

SonarQube

SAST

JFrog

SCA, Container

Configure your Jenkinsfile to install, run, and publish scans

Use the registerSecurityScan step to indicate which Security Scan Result must be sent to CloudBees platform.

Input name Data type Required? Description

artifacts

String

Yes

Security scan to include. Wildcards are supported.

format

String

No

sarif and json are the supported values. sarif is the default value.

scanner

String

No

If the format is not sarif, a parameter value must be specified. If a parameter value is not specified, the CloudBees platform ignores this security scan and the build is marked Unstable. CloudBees platform uses this to determine which scanner produces the security scan. The default value is empty.

archive

String

No

Describes if the reports must also be archived in the Jenkins build. The default value is true.

Example of a Pipeline stage including the step:

pipeline { stages { stage('Security Scan') { steps { registerSecurityScan( // Security Scan to include artifacts: "scan*", format: "sarif", scanner: "the-scanner", archive: true ) } } } }

Examples for scanners that support SARIF format

SARIF is a widely accepted standard used for sharing results from static analysis tools, especially in CI/CD environments.

The following examples illustrate how to use scanners that support SARIF format.

Black Duck sample Pipeline

Jenkinsfile example that uses the Black Duck scanner.

Add the following steps to your Jenkinsfile to install, run, and publish the results of a Black Duck scan to CloudBees platform.

pipeline { agent any environment { BRIDGE_CLI_DIR = "${WORKSPACE}/bridge-cli" BD_PROJECT_NAME = "my-blackduck-project" BD_VERSION_NAME = "1.0.0" GO_VERSION="1.21.2" BD_URL = credentials('BLACKDUCK_URL') BD_TOKEN = credentials('BLACKDUCK_API_TOKEN') } stages { stage('Download and Extract Bridge CLI') { (1) steps { sh ''' mkdir -p "$BRIDGE_CLI_DIR" # Download with error check curl -f -L "https://repo.blackduck.com/bds-integrations-release/com/blackduck/integration/bridge/binaries/bridge-cli-bundle/latest/bridge-cli-bundle-linux64.zip" \ -o bridge.zip # Extract with jar (no unzip needed) (cd "$BRIDGE_CLI_DIR" && jar -xf ../bridge.zip) # check if the binary exists ls -lrt ${BRIDGE_CLI_DIR} chmod +x "$BRIDGE_CLI_DIR"/bridge-cli-bundle-linux64/bridge-cli # Verify "$BRIDGE_CLI_DIR/bridge-cli-bundle-linux64/bridge-cli" --version ''' } } stage('Prepare Bridge CLI') { steps { sh ''' chmod -R +x bridge-cli/bridge-cli-bundle-linux64/adapters ''' } } stage('Run Black Duck Bridge CLI with SARIF Output') { (2) steps { sh """ curl -LO https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz rm -rf /tmp/go tar -C /tmp -xzf go${GO_VERSION}.linux-amd64.tar.gz export PATH=/tmp/go/bin:$PATH "${BRIDGE_CLI_DIR}/bridge-cli-bundle-linux64/bridge-cli" \ --stage blackducksca \ blackducksca.url="https://blackduck.example.com/" \ blackducksca.scan.full=true \ blackducksca.token="${BD_TOKEN}" \ blackducksca_reports_sarif_create=true \ blackducksca_reports_sarif_file_path="output/blackduck-sarif-report.sarif" \ blackducksca_reports_sarif_groupSCAIssues=false """ } } stage('Check the SARIF Report') { (3) steps { sh ''' echo "Checking SARIF report..." ls -l output/*.sarif cat output/*.sarif ''' } } stage('Archive SARIF Report') { steps { registerSecurityScan( artifacts: "output/*.sarif", format: "sarif", // can be omitted scanner: "BlackDuck", // can be omitted archive: true ) } } } }
1 Download and extract Bridge CLI. Use Black Duck Bridge, not Black Duck Detect, for scanning.
2 Run the scan with SARIF output. The shell command should be formed as above. You must include blackducksca_reports_sarif_groupSCAIssues=false.
3 Check the SARIF report.

Checkov sample Pipeline

Jenkinsfile example that uses the Checkov scanner.

Add the following steps to your Jenkinsfile to install, run, and publish the results of a Checkov scan to CloudBees platform.

pipeline { agent any environment { PYTHON_URL = "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7+20240107-x86_64-unknown-linux-gnu-install_only.tar.gz" PYTHON_DIR = "${env.WORKSPACE}/python" VENV_DIR = "${env.WORKSPACE}/venv" CHECKOV_REPORT = "checkov-report.sarif" CHECKOV_TARGET_DIR = "${env.WORKSPACE}/terragoat" CHECKOV_TARGET_FILE = "${env.WORKSPACE}/minimain.tf" CHECKOV_DISABLE_GUIDE = "true" BC_API_KEY = "" PRISMA_API_URL = "" } stages { // Step 1: Download and set up prebuilt Python binary stage('Download Prebuilt Python') { steps { echo ":arrow_down: Downloading prebuilt Python binary..." sh ''' mkdir -p $PYTHON_DIR cd $PYTHON_DIR curl -L -o python.tar.gz $PYTHON_URL tar -xzf python.tar.gz --strip-components=1 echo ":white_check_mark: Python extracted to: $PYTHON_DIR" ''' } } // Step 2: Verify Python & Pip installation stage('Verify Python & Pip') { steps { sh ''' $PYTHON_DIR/bin/python3.11 --version $PYTHON_DIR/bin/pip3.11 --version ''' } } // Step 3: Create Virtual Environment for Pipenv stage('Create Virtual Environment') { steps { echo "Creating virtual environment if missing..." sh ''' if [ ! -d "$VENV_DIR" ]; then $PYTHON_DIR/bin/python3.11 -m venv "$VENV_DIR" else echo "Virtualenv already exists." fi ''' } } // Step 4: Install Pipenv if missing stage('Install Pipenv if Missing') { steps { echo "Installing Pipenv if missing..." sh ''' source "$VENV_DIR/bin/activate" if ! pip show pipenv > /dev/null 2>&1; then pip install pipenv else echo "Pipenv already installed." fi ''' } } // Step 5: Install Checkov via Pipenv stage('Install Checkov via Pipenv') { (1) steps { echo "Installing Checkov using Pipenv..." sh ''' source "$VENV_DIR/bin/activate" pip install certifi pipenv install checkov echo "Checkov and certifi installed." ''' } } stage('Run Checkov Scan') { (2) steps { echo "Running Checkov scan on a specific file (main.tf)..." sh ''' source "$VENV_DIR/bin/activate" export SSL_CERT_FILE=$(python -m certifi) CHECKOV_DISABLE_GUIDE=true pipenv run checkov -f "$CHECKOV_TARGET_FILE" -o sarif > "$CHECKOV_REPORT" || true ''' } } stage('Display SARIF Report') { (3) steps { echo "Displaying SARIF report:" sh ''' echo "=== Checkov SARIF Report (First 20 lines) ===" head -n 20 "$CHECKOV_REPORT" ''' } } } stage('Archive Checkov Report') { steps { registerSecurityScan( artifacts: "${env.CHECKOV_REPORT}", format: "sarif", // can be omitted scanner: "Checkov", // can be omitted archive: true ) } } }
1 Install the scanner.
2 Run the scan.
3 Format the report in SARIF.

Examples for scanners that do not support SARIF format

The following examples illustrate how to use scanners that do not support SARIF format.

Anchore sample Pipeline

Jenkinsfile example that uses the Anchore scanner.

Add the following steps to your Jenkinsfile to install, run, and publish the results of an Anchore scan to CloudBees platform.

pipeline { agent any stages { stage('Registering build artifact') { steps { echo 'Registering the metadata' registerBuildArtifactMetadata( name: "test-artifact-demo", version: "1.0.1", type: "docker", url: "http://non:1111", digest: "6f637064707039346163663237383938", label: "prod" ) } } stage('Register Security Scan') { steps { script { if (fileExists("anchore-findings.json")) { echo "File exists, registering scan..." registerSecurityScan( artifacts: "anchore-findings.json", format: "JSON", scanner: "Anchore", archive: false ) } else { error "anchore-findings.json not found!" } } } } } post { always { echo 'Pipeline completed.' } failure { echo 'Build or tests failed!' } } }

SonarQube sample Pipeline

If you are using SonarQube and want to send results to your CloudBees platform instance, you must use the exportSonarQubeScan step to generate and download the scanning results from SonarQube in a single step, as SonarQube does not provide an easy way to do this.

Jenkinsfile example that uses the SonarQube scanner.

Add the following steps to your Jenkinsfile to install, run, and publish the results of a SonarQube scan to CloudBees platform.

pipeline {
    agent any

    environment {
        SONAR_HOST = "https://sonarqube.yourdomain.com"
        PROJECT_KEY = "sarif__bash_test_${env.BUILD_NUMBER}"
        SCANNER_VERSION = "5.0.1.3006"
        SCANNER_HOME = "${WORKSPACE}/sonar-scanner-5.0.1.3006"
        JAVA_HOME = "${WORKSPACE}/jdk17"
        PATH = "${WORKSPACE}/jdk17/bin:${PATH}"
        jq = "${WORKSPACE}/bin/jq"
    }

    stages {
        stage('Install JDK') {
            steps {
                sh '''
                  echo "Downloading JDK..."
                  curl -sLo openjdk.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.14%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.14_7.tar.gz
                  tar -xzf openjdk.tar.gz
                  rm -rf jdk17 && mv jdk-17* jdk17
                  mkdir -p ${WORKSPACE}/bin
                  if [ ! -f ${WORKSPACE}/bin/jq ]; then
                      echo "Downloading jq..."
                      curl -sLo ${WORKSPACE}/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
                      chmod +x ${WORKSPACE}/bin/jq
                  fi
                '''
            }
        }

        stage('Install SonarScanner CLI') {
            steps {
                sh """
                  if [ ! -d "sonar-scanner-${SCANNER_VERSION}-linux" ]; then
                    echo "Downloading Sonar Scanner CLI..."
                    curl -sLo scanner-sq.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SCANNER_VERSION}.zip
                    jar -xf scanner-sq.zip
                    rm scanner-sq.zip
                  else
                    echo "SonarScanner already installed."
                  fi
                """
            }
        }

        stage('SonarQube Analysis') {
            steps {
                withCredentials([string(credentialsId: 'sonarqube-preprod-token', variable: 'SONAR_TOKEN')]) {
                    sh """
                        chmod +x ${SCANNER_HOME}/bin/sonar-scanner
                        ${SCANNER_HOME}/bin/sonar-scanner \
                          -Dsonar.projectKey=$PROJECT_KEY \
                          -Dsonar.sources=. \
                          -Dsonar.host.url=$SONAR_HOST \
                          -Dsonar.login=$SONAR_TOKEN
                    """
                }
            }
        }

        stage('Wait for Analysis') {
            steps {
                withCredentials([string(credentialsId: 'sonarqube-preprod-token', variable: 'SONAR_TOKEN')]) {
                    script {
                        def reportTask = readFile '.scannerwork/report-task.txt'
                        def ceTaskUrl = reportTask.readLines()
                            .find { it.startsWith("ceTaskUrl=") }
                            .replace("ceTaskUrl=", "")

                        echo "Waiting for SonarQube CE task to complete: ${ceTaskUrl}"

                        timeout(time: 5, unit: 'MINUTES') {
                            waitUntil {
                                def result = sh(
                                    script: "curl -s -u ${SONAR_TOKEN}: ${ceTaskUrl} | $jq -r '.task.status'",
                                    returnStdout: true
                                ).trim()
                                echo "SonarQube CE task status: ${result}"
                                return (result == "SUCCESS")
                            }
                        }
                    }
                }
            }
        }

        stage('Export Sonar Findings') {
            steps{
                exportSonarQubeScan(
                    component: "",
                    project: "$PROJECT_KEY",
                    host: "$SONAR_HOST",
                    credentialId: "sonarqube-preprod-token"
                )
            }
        }
    }
}

Run an automatically triggered implicit scan of your source code

Implicit scanning in the CloudBees platform refers to the automatic security analysis of source code without requiring explicit user intervention. This process ensures continuous security checks by automatically triggering scans whenever certain events occur, such as:

  • The creation of a new component.

  • Committing changes in a connected repository.

  • Generating an artifact from a CI build.

Enable implicit security scanning to provide ongoing security monitoring.

Learn more

For more information on security management and monitoring in CloudBees platform, refer to: